diff --git a/.github/workflows/MoveToNextIteration.yml b/.github/workflows/MoveToNextIteration.yml new file mode 100644 index 000000000..2d79e5f59 --- /dev/null +++ b/.github/workflows/MoveToNextIteration.yml @@ -0,0 +1,20 @@ +on: + schedule: + # Runs "at 05:00, only on Tuesday" (see https://crontab.guru) + - cron: '0 5 * * 2' + +jobs: + move-to-next-iteration: + name: Move to next iteration + runs-on: ubuntu-latest + + steps: + - uses: blombard/move-to-next-iteration@master + with: + owner: rairprotocol + number: 1 + token: ${{ secrets.PROJECT_PAT }} + iteration-field: Iteration + iteration: last + new-iteration: current + excluded-statuses: "Done,Won't Fix" diff --git a/rair-front/src/components/MockUpPage/NftList/NftItemForCollectionView.tsx b/rair-front/src/components/MockUpPage/NftList/NftItemForCollectionView.tsx index 619a6484f..3f1ab5c06 100644 --- a/rair-front/src/components/MockUpPage/NftList/NftItemForCollectionView.tsx +++ b/rair-front/src/components/MockUpPage/NftList/NftItemForCollectionView.tsx @@ -3,17 +3,13 @@ import ReactPlayer from 'react-player'; import { Provider, useSelector, useStore } from 'react-redux'; import { useNavigate, useParams } from 'react-router-dom'; import axios, { AxiosError } from 'axios'; -import { BigNumber, constants, utils } from 'ethers'; +import { BigNumber } from 'ethers'; import { formatEther } from 'ethers/lib/utils'; -import { - IOffersResponseType, - TUserResponse -} from '../../../axios.responseTypes'; +import { IOffersResponseType } from '../../../axios.responseTypes'; import { RootState } from '../../../ducks'; import { ColorStoreType } from '../../../ducks/colors/colorStore.types'; import { ContractsInitialType } from '../../../ducks/contracts/contracts.types'; -import { UserType } from '../../../ducks/users/users.types'; import useIPFSImageLink from '../../../hooks/useIPFSImageLink'; import useSwal from '../../../hooks/useSwal'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; @@ -61,7 +57,6 @@ const NftItemForCollectionViewComponent: React.FC< const navigate = useNavigate(); const store = useStore(); - const [userInfoMinted, setUserInfoMinted] = useState(null); const [isFileUrl, setIsFileUrl] = useState(); const ipfsLink = useIPFSImageLink(metadata?.image); const [tokenInfo, setTokenInfo] = useState(null); @@ -199,20 +194,6 @@ const NftItemForCollectionViewComponent: React.FC< } }, [navigate, tokenInfo, contract, blockchain, product, index, item]); - const getInfoFromUser = useCallback(async () => { - // find user - if ( - item && - utils.isAddress(item.ownerAddress) && - item.ownerAddress !== constants.AddressZero - ) { - const result = await axios - .get(`/api/users/${item.ownerAddress}`) - .then((res) => res.data); - setUserInfoMinted(result.user); - } - }, [item]); - const initialTokenData = useCallback(() => { if (item && resaleFlag) { if (item.contract?.diamond) { @@ -313,10 +294,6 @@ const NftItemForCollectionViewComponent: React.FC< getParticularOffer(); }, [getParticularOffer]); - useEffect(() => { - getInfoFromUser(); - }, [getInfoFromUser]); - useEffect(() => { checkUrl(); }, [checkUrl]); @@ -517,25 +494,25 @@ const NftItemForCollectionViewComponent: React.FC< maxHeight: '40px' }}>
- {item?.isMinted && userInfoMinted ? ( + {item?.isMinted && item.ownerData ? (
User Avatar
- {userInfoMinted.nickName - ? userInfoMinted.nickName.length > 16 - ? userInfoMinted.nickName.slice(0, 5) + + {item.ownerData?.nickName + ? item.ownerData?.nickName.length > 16 + ? item.ownerData?.nickName.slice(0, 5) + '...' + - userInfoMinted.nickName.slice( - userInfoMinted.nickName.length - 4 + item.ownerData?.nickName.slice( + item.ownerData?.nickName.length - 4 ) - : userInfoMinted.nickName + : item.ownerData?.nickName : userName?.slice(0, 5) + '....' + userName?.slice(userName.length - 4)} @@ -578,10 +555,10 @@ const NftItemForCollectionViewComponent: React.FC< )}
{item && !resaleFlag && item.isMinted && !resalePrice && ( -
-
Sold out
-
- )} +
+
Sold out
+
+ )}
diff --git a/rair-node/bin/api/nft/nft.Controller.js b/rair-node/bin/api/nft/nft.Controller.js index b5c6ef4ce..ba799b0f9 100644 --- a/rair-node/bin/api/nft/nft.Controller.js +++ b/rair-node/bin/api/nft/nft.Controller.js @@ -55,11 +55,17 @@ router.post( router.get( '/network/:networkId/:contract/:product', validation(['nftContract', 'nftProduct'], 'params'), - findContractMiddleware, - findProductMiddleware, - validation(['getTokensByContractProduct', 'resaleFlag', 'metadataSearch'], 'query'), + findContractAndProductMiddleware, + validation(['getTokensByContractProduct', 'resaleFlag', 'metadataSearch', 'tokenLimits'], 'query'), getTokensForProduct, ); +router.get( + '/network/:networkId/:contract/:product/numbers', + validation(['nftContract', 'nftProduct'], 'params'), + validation(['tokenLimits'], 'query'), + findContractAndProductMiddleware, + getTokenNumbers, +); router.get( '/network/:networkId/:contract/:product/attributes', validation(['nftContract', 'nftProduct'], 'params'), diff --git a/rair-node/bin/api/nft/nft.Service.js b/rair-node/bin/api/nft/nft.Service.js index 22b721938..ffba53a49 100644 --- a/rair-node/bin/api/nft/nft.Service.js +++ b/rair-node/bin/api/nft/nft.Service.js @@ -347,23 +347,89 @@ module.exports = { next(err); } }, - findProductMiddleware: async (req, res, next) => { - try { - const product = await Product.findOne({ - contract: req.contract._id, - collectionIndexInContract: req.params.product, - }); - - if (!product) { - return next(new AppError('Product not found.', 404)); - } - - req.product = product; - - return next(); - } catch (e) { - return next(e); + getTokenNumbers: async (req, res, next) => { + try { + const { contract, product } = req; + const { fromToken, toToken } = req.query; + const tokenLimitFilter = []; + if (fromToken) { + tokenLimitFilter.push( + { + $gte: ['$token', fromToken], + }, + ); } + if (toToken) { + tokenLimitFilter.push( + { + $lte: ['$token', toToken], + }, + ); + } + const offerData = await Offer.aggregate([ + { + $match: { + $expr: { + $eq: [contract._id, '$contract'], + }, + product: product.collectionIndexInContract, + }, + }, + { + $lookup: { + from: 'MintedToken', + let: { offerIndex: '$diamondRangeIndex' }, + as: 'tokens', + pipeline: [{ + $match: { + $expr: { + $and: [ + { + $eq: ['$offer', '$$offerIndex'], + }, + ...tokenLimitFilter, + ], + }, + contract: contract._id, + }, + }, + { + $sort: { uniqueIndexInContract: 1 }, + }, + { + $addFields: { + sold: { + $cond: { + if: { $eq: ['$ownerAddress', ZeroAddress] }, + then: false, + else: true, + }, + }, + }, + }, + { + $project: { + _id: 0, + token: 1, + sold: 1, + }, + }, + ], + }, + }, + { + $project: { + tokens: 1, + }, + }, + ]).collation({ locale: 'en_US', numericOrdering: true }); + return res.json({ + success: true, + tokens: offerData.reduce((total, offer) => total.concat(offer.tokens), []), + }); + } catch (err) { + return next(err); + } }, getProductAttributes: async (req, res, next) => { try { @@ -499,20 +565,34 @@ module.exports = { }, }, { - $lookup: { - from: 'Offer', - let: populateOptions.let, - pipeline: [ - { - $match: { - $expr: { - $and: populateOptions.and, - }, - }, - }, - ], - as: 'offer', + $lookup: { + from: 'Offer', + let: populateOptions.let, + pipeline: [ + { + $match: { + $expr: { + $and: populateOptions.and, + }, + }, + }, + ], + as: 'offer', + }, + }, + { + $lookup: { + from: 'User', + localField: 'ownerAddress', + foreignField: 'publicAddress', + as: 'ownerData', + }, }, + { + $unwind: { + path: '$ownerData', + preserveNullAndEmptyArrays: true, + }, }, { $unwind: '$offer' }, { $match: filterOptions }, diff --git a/rair-node/bin/api/notifications/notifications.Controller.js b/rair-node/bin/api/notifications/notifications.Controller.js index d032d98d3..0186af7b8 100644 --- a/rair-node/bin/api/notifications/notifications.Controller.js +++ b/rair-node/bin/api/notifications/notifications.Controller.js @@ -1,6 +1,11 @@ const express = require('express'); const { requireUserSession, validation } = require('../../middleware'); -const { markNotificationAsRead, getSingleNotification, listNotifications, deleteNotification } = require('./notifications.Service'); +const { + markNotificationAsRead, + getSingleNotification, + listNotifications, + deleteNotification, +} = require('./notifications.Service'); const router = express.Router(); diff --git a/rair-node/bin/api/notifications/notifications.Service.js b/rair-node/bin/api/notifications/notifications.Service.js index 212bb51d1..1daea790d 100644 --- a/rair-node/bin/api/notifications/notifications.Service.js +++ b/rair-node/bin/api/notifications/notifications.Service.js @@ -59,11 +59,18 @@ module.exports = { }, markNotificationAsRead: async (req, res, next) => { try { - const { id } = req.params; - const notification = await Notification.findByIdAndUpdate(id, { $set: { read: true } }); - if (!notification) { - return next(new AppError('Notification not found', 404)); + const { publicAddress } = req.user; + const { ids = [] } = req.body; + const filter = { + user: publicAddress, + }; + if (ids?.length) { + filter._id = { $in: ids }; } + const result = await Notification.updateMany( + filter, + { $set: { read: true } }, + ); return res.json({ success: true, notification, @@ -75,11 +82,15 @@ module.exports = { }, deleteNotification: async (req, res, next) => { try { - const { id } = req.params; - const notification = await Notification.findByIdAndDelete(id); - if (!notification) { - return next(new AppError('Notification not found', 404)); + const { publicAddress } = req.user; + const { ids = [] } = req.body; + const filter = { + user: publicAddress, + }; + if (ids?.length) { + filter._id = { $in: ids }; } + const result = await Notification.deleteMany(filter); return res.json({ success: true, notification, diff --git a/rair-node/bin/schemas/commonApiSchemas.js b/rair-node/bin/schemas/commonApiSchemas.js index 76173d589..17fc2aba6 100644 --- a/rair-node/bin/schemas/commonApiSchemas.js +++ b/rair-node/bin/schemas/commonApiSchemas.js @@ -24,4 +24,8 @@ module.exports = { metadataSearch: () => ({ metadataFilters: Joi.string(), }), + tokenLimits: () => ({ + fromToken: Joi.string(), + toToken: Joi.string(), + }), }; diff --git a/rair-node/bin/schemas/getTokensByContractProduct.js b/rair-node/bin/schemas/getTokensByContractProduct.js index ad6bf6f2f..1e49afabc 100644 --- a/rair-node/bin/schemas/getTokensByContractProduct.js +++ b/rair-node/bin/schemas/getTokensByContractProduct.js @@ -1,8 +1,6 @@ const Joi = require('joi'); module.exports = () => ({ - fromToken: Joi.string(), - toToken: Joi.string(), limit: Joi.number(), forSale: Joi.any() .valid('true', 'false'), diff --git a/rair-node/bin/schemas/index.js b/rair-node/bin/schemas/index.js index efb2b5715..606dd71a0 100644 --- a/rair-node/bin/schemas/index.js +++ b/rair-node/bin/schemas/index.js @@ -55,6 +55,7 @@ const { userAddress, resaleFlag, metadataSearch, + tokenLimits, } = require('./commonApiSchemas'); const { fullContracts, @@ -137,6 +138,7 @@ module.exports = { userAddress, resaleFlag, metadataSearch, + tokenLimits, // Contract schemas fullContracts,