diff --git a/src/api/controllers/nfts/controller.ts b/src/api/controllers/nfts/controller.ts index 42e6abb..9264204 100644 --- a/src/api/controllers/nfts/controller.ts +++ b/src/api/controllers/nfts/controller.ts @@ -1,6 +1,6 @@ import NFTService from "../../services/nft"; import { NextFunction, Request, Response } from "express"; -import { validationGetNFTs, validationGetNFT, validationGetStatNFTsUser, validationNFTsBySeries, validationGetSeries, validationCanAddToSeries, validationGetHistory, validationAddCategoriesNFTs, validationGetTotalOnSale, validationLikeUnlike, validationGetFilters } from "../../validators/nftValidators"; +import { validationGetNFTs, validationGetNFT, validationGetStatNFTsUser, validationNFTsBySeries, validationGetSeries, validationCanAddToSeries, validationGetHistory, validationAddCategoriesNFTs, validationGetTotalOnSale, validationGetTotalFilteredNFTs, validationLikeUnlike, validationGetFilters } from "../../validators/nftValidators"; import { decryptCookie } from "../../../utils"; export class Controller { @@ -117,6 +117,19 @@ export class Controller { } } + async getTotalFiltered( + req: Request, + res: Response, + next: NextFunction + ): Promise{ + try { + const queryValues = validationGetTotalFilteredNFTs(req.query) + res.json(await NFTService.getTotalFilteredNFTs(queryValues)); + } catch (err) { + next(err); + } + } + async likeNft( req: Request, res: Response, diff --git a/src/api/controllers/nfts/router.ts b/src/api/controllers/nfts/router.ts index 7d17c49..fd33843 100644 --- a/src/api/controllers/nfts/router.ts +++ b/src/api/controllers/nfts/router.ts @@ -9,6 +9,7 @@ export default express .get("/most-sold-series", controller.getMostSoldSeries) .get("/history", controller.getHistory) .get("/total-on-sale", controller.getTotalOnSale) + .get("/total-filtered", controller.getTotalFiltered) .get("/:id", controller.getNFT) .get("/stat/:id", controller.getStatNFTsUser) .get("/series/data", controller.getNFTsBySeries) diff --git a/src/api/helpers/nftHelpers.ts b/src/api/helpers/nftHelpers.ts index aa22b81..e67aacd 100644 --- a/src/api/helpers/nftHelpers.ts +++ b/src/api/helpers/nftHelpers.ts @@ -44,15 +44,14 @@ export async function populateStat( ): Promise<{ totalNft: number, totalListedNft: number, + totalFiltered: number | null, totalListedInMarketplace: number, totalOwnedByRequestingUser: number, totalOwnedListedByRequestingUser: number, smallestPrice: string }> { try { - const marketplaceId = query.filter?.marketplaceId; - const owner = query.filter?.owner; - const stat = await NFTService.getStatNFT(NFT.serieId, marketplaceId, owner) + const stat = await NFTService.getStatNFT(NFT.serieId, query) return stat } catch (err) { L.error({ err }, "NFTs stats could not have been fetched"); diff --git a/src/api/services/gqlQueriesBuilder.ts b/src/api/services/gqlQueriesBuilder.ts index 081603b..f76d9a6 100644 --- a/src/api/services/gqlQueriesBuilder.ts +++ b/src/api/services/gqlQueriesBuilder.ts @@ -1,6 +1,6 @@ import { gql } from "graphql-request"; import { convertSortString, convertSortStringDistinct, LIMIT_MAX_PAGINATION } from "../../utils"; -import { getFiltersQuery, getHistoryQuery, getSeriesStatusQuery, NFTBySeriesQuery, NFTQuery, NFTsQuery, statNFTsUserQuery } from "../validators/nftValidators"; +import { getFiltersQuery, getHistoryQuery, getSeriesStatusQuery, NFTBySeriesQuery, NFTQuery, NFTsQuery, statNFTsUserQuery, getTotalFilteredNFTsQuery } from "../validators/nftValidators"; // import L from '../../common/logger'; const nodes = ` @@ -427,6 +427,55 @@ countAllListedInMarketplace = (marketplaceId: number) => gql` } `; +countTotalFilteredNFTs = (query: getTotalFilteredNFTsQuery, seriesId?: string) => { + const { + idsCategories, + idsToExcludeCategories, + marketplaceId, + listed, + priceStartRange, + priceEndRange, + timestampCreateStartRange, + timestampCreateEndRange, + } = query.filter ?? {}; + + return gql` + { + nftEntities( + filter: { + and: [ + { timestampBurn: { isNull: true } } + ${seriesId!==undefined ? `{ serieId: { equalTo: "${seriesId}" } }` : ""} + ${listed!==undefined ? `{ listed: { equalTo: ${!listed ? 0 : 1} } }` : ""} + ${marketplaceId!==undefined ? `{ marketplaceId: { equalTo: "${marketplaceId}" } }` : ""} + ${idsCategories ? `{id: { in: ${JSON.stringify(idsCategories.map(x => String(x)))} }}` : ""} + ${idsToExcludeCategories ? `{id: { notIn: ${JSON.stringify(idsToExcludeCategories.map(x => String(x)))} }}` : ""} + ] + ${ + priceStartRange !== undefined || + priceEndRange !== undefined + ? `priceRounded: { + ${priceStartRange!==undefined ? `greaterThanOrEqualTo: ${priceStartRange}` : ""} + ${priceStartRange!==undefined ? `lessThanOrEqualTo: ${priceEndRange}` : ""} + }` + : "" + } + ${ + timestampCreateStartRange !== undefined || + timestampCreateEndRange !== undefined + ? `timestampCreate: { + ${timestampCreateStartRange!==undefined ? `greaterThanOrEqualTo: "${timestampCreateStartRange}"` : ""} + ${timestampCreateEndRange!==undefined ? `lessThanOrEqualTo: "${timestampCreateEndRange}"` : ""} + }` + : "" + } + } + ) { + totalCount + } + }`; +}; + getMostSold = (query: getFiltersQuery) => gql` { mostSold( diff --git a/src/api/services/nft.ts b/src/api/services/nft.ts index c8b6544..1594812 100644 --- a/src/api/services/nft.ts +++ b/src/api/services/nft.ts @@ -8,7 +8,7 @@ import CategoryService from "./category" import { populateNFT } from "../helpers/nftHelpers"; import QueriesBuilder from "./gqlQueriesBuilder"; import { decryptCookie, TIME_BETWEEN_SAME_USER_VIEWS } from "../../utils"; -import { canAddToSeriesQuery, addCategoriesNFTsQuery, getHistoryQuery, getSeriesStatusQuery, NFTBySeriesQuery, NFTQuery, NFTsQuery, statNFTsUserQuery, getTotalOnSaleQuery, likeUnlikeQuery, getFiltersQuery } from "../validators/nftValidators"; +import { canAddToSeriesQuery, addCategoriesNFTsQuery, getHistoryQuery, getSeriesStatusQuery, NFTBySeriesQuery, NFTQuery, NFTsQuery, statNFTsUserQuery, getTotalOnSaleQuery, getTotalFilteredNFTsQuery, likeUnlikeQuery, getFiltersQuery } from "../validators/nftValidators"; import CategoryModel from "../../models/category"; import { ICategory } from "../../interfaces/ICategory"; import { INftLike } from "../../interfaces/INftLike"; @@ -147,30 +147,37 @@ export class NFTService { * @param seriesId - Series Id of nft to get stat for * @param marketplaceId - marketplace id (optional) * @param owner - owner (optional) + * @param filterOptionsQuery - filter options (optional) * @throws Will throw an error if can't request indexer */ - async getStatNFT(seriesId:string, marketplaceId:number=null, owner:string=null): Promise<{ + async getStatNFT(seriesId:string, query:NFTsQuery=null): Promise<{ totalNft: number, totalListedNft: number, + totalFiltered: number | null, totalListedInMarketplace: number, totalOwnedByRequestingUser: number, totalOwnedListedByRequestingUser: number, totalOwnedListedInMarketplaceByRequestingUser: number, smallestPrice: string }> { + const marketplaceId = query.filter.marketplaceId ?? null; + const owner = query.filter.owner ?? null; + try { - const [totalRequest, totalListedRequest, totalListedInMarketplaceRequest, totalOwnedByRequestingUserRequest, totalOwnedListedByRequestingUserRequest, totalOwnedListedInMarketplaceByRequestingUserRequest, smallestPriceRequest] = await Promise.all([ + const [totalRequest, totalListedRequest, totalFilteredRequest, totalListedInMarketplaceRequest, totalOwnedByRequestingUserRequest, totalOwnedListedByRequestingUserRequest, totalOwnedListedInMarketplaceByRequestingUserRequest, smallestPriceRequest] = await Promise.all([ request(indexerUrl, QueriesBuilder.countTotal(seriesId)), request(indexerUrl, QueriesBuilder.countTotalListed(seriesId)), - marketplaceId!==null ? request(indexerUrl, QueriesBuilder.countTotalListedInMarketplace(seriesId, marketplaceId)) : 0, - owner ? request(indexerUrl, QueriesBuilder.countTotalOwned(seriesId, owner)) : null, - owner ? request(indexerUrl, QueriesBuilder.countTotalOwnedListed(seriesId, owner)) : null, - owner && marketplaceId!==null ? request(indexerUrl, QueriesBuilder.countTotalOwnedListedInMarketplace(seriesId, owner, marketplaceId)) : null, + query ? request(indexerUrl, QueriesBuilder.countTotalFilteredNFTs(query, seriesId)) : null, + marketplaceId !== null ? request(indexerUrl, QueriesBuilder.countTotalListedInMarketplace(seriesId, marketplaceId)) : 0, + owner !== null ? request(indexerUrl, QueriesBuilder.countTotalOwned(seriesId, owner)) : null, + owner !== null ? request(indexerUrl, QueriesBuilder.countTotalOwnedListed(seriesId, owner)) : null, + owner !== null && marketplaceId !== null ? request(indexerUrl, QueriesBuilder.countTotalOwnedListedInMarketplace(seriesId, owner, marketplaceId)) : null, request(indexerUrl, QueriesBuilder.countSmallestPrice(seriesId, marketplaceId)), ]) const totalNft: number = totalRequest.nftEntities.totalCount; const totalListedNft: number = totalListedRequest.nftEntities.totalCount; const totalListedInMarketplace: number = totalListedInMarketplaceRequest ? totalListedInMarketplaceRequest.nftEntities.totalCount : 0; + const totalFiltered: number = totalFilteredRequest ? totalFilteredRequest.nftEntities.totalCount : null; const totalOwnedByRequestingUser: number = totalOwnedByRequestingUserRequest ? totalOwnedByRequestingUserRequest.nftEntities.totalCount : 0; const totalOwnedListedByRequestingUser: number = totalOwnedListedByRequestingUserRequest ? totalOwnedListedByRequestingUserRequest.nftEntities.totalCount : 0; const totalOwnedListedInMarketplaceByRequestingUser: number = totalOwnedListedInMarketplaceByRequestingUserRequest ? totalOwnedListedInMarketplaceByRequestingUserRequest.nftEntities.totalCount : 0; @@ -179,7 +186,7 @@ export class NFTService { : "0" ; - return { totalNft, totalListedNft, totalListedInMarketplace, totalOwnedByRequestingUser, totalOwnedListedByRequestingUser, totalOwnedListedInMarketplaceByRequestingUser, smallestPrice } + return { totalNft, totalListedNft, totalListedInMarketplace, totalFiltered, totalOwnedByRequestingUser, totalOwnedListedByRequestingUser, totalOwnedListedInMarketplaceByRequestingUser, smallestPrice } } catch (err) { throw new Error("Couldn't get NFT stat"); } @@ -354,6 +361,25 @@ export class NFTService { } } + /** + * Returns the totalCount for the specified filters + * @param query - query (see getTotalFilteredNFTsQuery) + * @throws Will throw an error if indexer is not reachable + */ + async getTotalFilteredNFTs(query: getTotalFilteredNFTsQuery): Promise { + try { + // Categories + if (query.filter?.categories) await this.handleFilterCategory(query); + + const gqlQuery = QueriesBuilder.countTotalFilteredNFTs(query); + const res = await request(indexerUrl, gqlQuery); + if (!res.nftEntities.totalCount) throw new Error(); + return res.nftEntities.totalCount; + } catch (err) { + throw new Error("Filtered count could not have been fetched"); + } + } + /** * Like an NFT * @param query - see likeUnlikeQuery diff --git a/src/api/validators/nftValidators.ts b/src/api/validators/nftValidators.ts index ec8d76d..ba6259e 100644 --- a/src/api/validators/nftValidators.ts +++ b/src/api/validators/nftValidators.ts @@ -273,4 +273,34 @@ export const validationGetFilters = (query: any) => { }).required() }) return validateQuery(validationSchema, { pagination }) as getFiltersQuery; +} + +export type getTotalFilteredNFTsQuery = { + filter?: { + idsCategories?: string[], + idsToExcludeCategories?: string[], + categories?: string[], + listed?: boolean, + marketplaceId?: number, + priceStartRange?: number, + priceEndRange?: number, + timestampCreateStartRange?: Date, + timestampCreateEndRange?: Date + } +} +export const validationGetTotalFilteredNFTs = (query: any) => { + let { filter } = query; + if (filter) filter = JSON.parse(filter); + const validationSchema = Joi.object({ + filter: Joi.object({ + categories: Joi.array().items(Joi.string()), + listed: Joi.boolean(), + marketplaceId: Joi.number().integer().min(0), + priceStartRange: Joi.number(), + priceEndRange: Joi.number(), + timestampCreateStartRange: Joi.date().raw(), + timestampCreateEndRange: Joi.date().raw(), + }), + }) + return validateQuery(validationSchema, { filter }) as getTotalFilteredNFTsQuery; } \ No newline at end of file