Skip to content

Commit

Permalink
move topseller route in user services, add most followed route
Browse files Browse the repository at this point in the history
  • Loading branch information
Leouarz committed Feb 10, 2022
1 parent 2ceb743 commit a113d69
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 56 deletions.
13 changes: 0 additions & 13 deletions src/api/controllers/nfts/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,6 @@ export class Controller {
next(err)
}
}

async getTopSellers(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const queryValues = validationGetFilters(req.query)
res.json(await NFTService.getTopSellers(queryValues));
} catch (err) {
next(err)
}
}
}

export default new Controller();
1 change: 0 additions & 1 deletion src/api/controllers/nfts/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export default express
.get("/most-viewed", controller.getMostViewed)
.get("/most-sold", controller.getMostSold)
.get("/most-sold-series", controller.getMostSoldSeries)
.get("/top-sellers", controller.getTopSellers)
.get("/history", controller.getHistory)
.get("/total-on-sale", controller.getTotalOnSale)
.get("/:id", controller.getNFT)
Expand Down
28 changes: 27 additions & 1 deletion src/api/controllers/users/controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UserService from "../../services/user";
import { NextFunction, Request, Response } from "express";
import { TERNOA_API_URL, decryptCookie } from "../../../utils";
import { validationGetAccountBalance, validationGetUser, validationReviewRequested, validationGetUsers } from "../../validators/userValidators";
import { validationGetAccountBalance, validationGetUser, validationReviewRequested, validationGetUsers, validationGetFilters } from "../../validators/userValidators";

export class Controller {
async getUsers(
Expand Down Expand Up @@ -99,5 +99,31 @@ export class Controller {
next(err)
}
}

async getTopSellers(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const queryValues = validationGetFilters(req.query)
res.json(await UserService.getTopSellers(queryValues));
} catch (err) {
next(err)
}
}

async getMostFollowed(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const queryValues = validationGetFilters(req.query)
res.json(await UserService.getMostFollowed(queryValues));
} catch (err) {
next(err)
}
}
}
export default new Controller();
2 changes: 2 additions & 0 deletions src/api/controllers/users/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import controller from "./controller";
export default express
.Router()
.patch("/reviewRequested/:id", controller.reviewRequested) // ternoa-api
.get("/top-sellers", controller.getTopSellers) // ternoa-api
.get("/most-followed", controller.getMostFollowed) // ternoa-api
.get("/", controller.getUsers) // ternoa-api
.get("/verifyTwitter/:id", controller.verifyTwitter) // ternoa-api
.get("/:id", controller.getUser) // ternoa-api
Expand Down
4 changes: 2 additions & 2 deletions src/api/helpers/nftHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { CustomResponse, ICompleteNFT, INFT } from "../../interfaces/graphQL";
import { ICompleteNFT, INFT } from "../../interfaces/graphQL";
import UserService from "../services/user";
import L from "../../common/logger";
import NFTService from "../services/nft";
import { ICategory } from "../../interfaces/ICategory";
import { fetchTimeout, isURL, removeURLSlash } from "../../utils";
import { IUser } from "src/interfaces/IUser";
import { IUser } from "../../interfaces/IUser";
import { NFTsQuery } from "../validators/nftValidators";

const ipfsGateways = {
Expand Down
37 changes: 2 additions & 35 deletions src/api/services/nft.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { request } from "graphql-request";
import fetch from "node-fetch";
import { DistinctNFTListResponse, INFT, NFTListResponse, CustomResponse, ISeries, INFTTransfer } from "../../interfaces/graphQL";
import FollowModel from "../../models/follow";
import NftModel from "../../models/nft";
Expand All @@ -8,12 +7,11 @@ import NftLikeModel from "../../models/nftLike";
import CategoryService from "./category"
import { populateNFT } from "../helpers/nftHelpers";
import QueriesBuilder from "./gqlQueriesBuilder";
import { decryptCookie, TERNOA_API_URL, TIME_BETWEEN_SAME_USER_VIEWS } from "../../utils";
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 CategoryModel from "../../models/category";
import { ICategory } from "../../interfaces/ICategory";
import { INftLike } from "src/interfaces/INftLike";
import { IUser } from "src/interfaces/IUser";
import { INftLike } from "../../interfaces/INftLike";

const indexerUrl = process.env.INDEXER_URL || "https://indexer.chaos.ternoa.com";

Expand Down Expand Up @@ -514,37 +512,6 @@ export class NFTService {
throw new Error("Couldn't get most sold series");
}
}

/**
* Get top sellers account address sorted by best sellers
* @param query - see getFiltersQuery
* @throws Will throw an error if indexer or db can't be reached
*/
async getTopSellers(query: getFiltersQuery): Promise<CustomResponse<IUser>> {
try {
const gqlQuery = QueriesBuilder.getTopSellers(query);
const res = await request(indexerUrl, gqlQuery);
const topSellers: {id: string, occurences: number}[] = res.topSeller.nodes;
const topSellersSorted = topSellers.map(x => x.id)
const filterDbUser = {walletIds: topSellersSorted}
const resDbUsers = await fetch(`${TERNOA_API_URL}/api/users/?filter=${JSON.stringify(filterDbUser)}`)
const dbUsers: CustomResponse<IUser> = await resDbUsers.json()
const data = topSellersSorted.map(x => {
let user = dbUsers.data.find(y => y.walletId === x)
if (user === undefined) user = {_id: x, walletId: x}
return user
})
const result: CustomResponse<IUser> = {
totalCount: res.topSeller.totalCount,
data,
hasNextPage: res.topSeller.pageInfo.hasNextPage,
hasPreviousPage: res.topSeller.pageInfo.hasPreviousPage
}
return result
} catch (err) {
throw new Error("Couldn't get top sellers");
}
}
}

export default new NFTService();
63 changes: 63 additions & 0 deletions src/api/services/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import fetch from "node-fetch";
import { IUser } from "../../interfaces/IUser";
import UserViewModel from "../../models/userView";
import NftLikeModel from "../../models/nftLike";
import FollowModel from "../../models/follow";
import QueriesBuilder from "./gqlQueriesBuilder";
import { AccountResponse, Account, CustomResponse } from "../../interfaces/graphQL";
import { TIME_BETWEEN_SAME_USER_VIEWS, TERNOA_API_URL } from "../../utils";
import { getAccountBalanceQuery, getUserQuery, getUsersQuery } from "../validators/userValidators";
import { getFiltersQuery } from "../validators/nftValidators";

const indexerUrl =
process.env.INDEXER_URL || "https://indexer.chaos.ternoa.com";
Expand Down Expand Up @@ -93,6 +95,67 @@ export class UserService {
}
}

/**
* Get top sellers account address sorted by best sellers
* @param query - see getFiltersQuery
* @throws Will throw an error if indexer or db can't be reached
*/
async getTopSellers(query: getFiltersQuery): Promise<CustomResponse<IUser>> {
try {
const gqlQuery = QueriesBuilder.getTopSellers(query);
const res = await request(indexerUrl, gqlQuery);
const topSellers: {id: string, occurences: number}[] = res.topSeller.nodes;
const topSellersSorted = topSellers.map(x => x.id)
const filterDbUser = {walletIds: topSellersSorted}
const resDbUsers = await fetch(`${TERNOA_API_URL}/api/users/?filter=${JSON.stringify(filterDbUser)}`)
const dbUsers: CustomResponse<IUser> = await resDbUsers.json()
const data = topSellersSorted.map(x => {
let user = dbUsers.data.find(y => y.walletId === x)
if (user === undefined) user = {_id: x, walletId: x}
return user
})
const result: CustomResponse<IUser> = {
totalCount: res.topSeller.totalCount,
data,
hasNextPage: res.topSeller.pageInfo.hasNextPage,
hasPreviousPage: res.topSeller.pageInfo.hasPreviousPage
}
return result
} catch (err) {
throw new Error("Couldn't get top sellers");
}
}

/**
* get most followed users sorted by number of follows
* @param query - see getFiltersQuery
* @throws Will throw an error if db can't be reached
*/
async getMostFollowed(query: getFiltersQuery): Promise<CustomResponse<IUser>> {
try{
const aggregateQuery = [{ $group: { _id: "$followed", totalViews: { $sum: 1 } } }]
const aggregate = FollowModel.aggregate(aggregateQuery);
const res = await FollowModel.aggregatePaginate(aggregate, {page: query.pagination.page, limit: query.pagination.limit, sort:{totalViews: -1}})
const walletIdsSorted = res.docs.map(x => x._id)
const filterDbUser = {walletIds: walletIdsSorted}
const resDbUsers = await fetch(`${TERNOA_API_URL}/api/users/?filter=${JSON.stringify(filterDbUser)}`)
const dbUsers: CustomResponse<IUser> = await resDbUsers.json()
const data = walletIdsSorted.map(x => {
let user = dbUsers.data.find(y => y.walletId === x)
if (user === undefined) user = {_id: x, walletId: x}
return user
})
const result: CustomResponse<IUser> = {
totalCount: res.totalDocs,
data,
hasNextPage: res.hasNextPage,
hasPreviousPage: res.hasPrevPage
}
return result
}catch(err){
throw err
}
}
}

export default new UserService();
19 changes: 19 additions & 0 deletions src/api/validators/userValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,23 @@ export const validationGetAccountBalance = (query: any) => {
id: Joi.string().required(),
});
return validateQuery(validationSchema, query) as getAccountBalanceQuery;
};


export type getFiltersQuery = {
pagination: {
page: number;
limit: number;
};
};
export const validationGetFilters = (query: any) => {
let { pagination } = query;
if (pagination) pagination = JSON.parse(pagination);
const validationSchema = Joi.object({
pagination: Joi.object({
page: Joi.number().integer().min(0).required(),
limit: Joi.number().integer().min(0).max(LIMIT_MAX_PAGINATION).required(),
}).required(),
});
return validateQuery(validationSchema, { pagination }) as getFiltersQuery;
};
8 changes: 4 additions & 4 deletions src/models/follow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import mongoose, { PaginateModel } from "mongoose";
import mongoose, {AggregatePaginateModel} from "mongoose";
import { IFollow } from "../interfaces/IFollow";
import mongoosePaginate from "mongoose-paginate-v2";
import aggregatePaginate from "mongoose-aggregate-paginate-v2";

/* based on socialite implementation https://github.com/mongodb-labs/socialite/blob/master/docs/graph.md */

Expand All @@ -15,11 +15,11 @@ const Follow = new mongoose.Schema({
},
});

Follow.plugin(mongoosePaginate);
Follow.plugin(aggregatePaginate);

const FollowModel = mongoose.model<IFollow & mongoose.Document>(
"Follow",
Follow
) as PaginateModel<IFollow & mongoose.Document>;
) as unknown as AggregatePaginateModel<IFollow & mongoose.Document>;

export default FollowModel;

0 comments on commit a113d69

Please sign in to comment.