Skip to content

Commit

Permalink
refacto likes
Browse files Browse the repository at this point in the history
  • Loading branch information
Leouarz committed Feb 2, 2022
1 parent 10556a8 commit 81d557b
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 97 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


"SecretNFT API" is the server used by the "SecretNFT" application.
This API's purpose is to get data related to NFTs (from blockahin to offchain Data), users, follows, likes, ...
This API's purpose is to get data related to NFTs (from blockahin to offchain Data), users ...

Visit [SecretNFT](https://www.secret-nft.com/)

Expand Down
39 changes: 38 additions & 1 deletion src/api/controllers/nfts/controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import NFTService from "../../services/nft";
import { NextFunction, Request, Response } from "express";
import { validationGetNFTs, validationGetNFT, validationGetStatNFTsUser, validationNFTsBySeries, validationGetSeries, validationCanAddToSeries, validationGetHistory, validationAddCategoriesNFTs, validationGetTotalOnSale } from "../../validators/nftValidators";
import { validationGetNFTs, validationGetNFT, validationGetStatNFTsUser, validationNFTsBySeries, validationGetSeries, validationCanAddToSeries, validationGetHistory, validationAddCategoriesNFTs, validationGetTotalOnSale, validationLikeUnlike } from "../../validators/nftValidators";
import { decryptCookie } from "src/utils";

export class Controller {
async getNFTs(
Expand Down Expand Up @@ -115,6 +116,42 @@ export class Controller {
next(err);
}
}

async likeNft(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { cookie } = JSON.parse(req.body)
const queryValues = validationLikeUnlike({cookie, ...req.query})
if(decryptCookie(queryValues.cookie) === queryValues.walletId){
res.json(await NFTService.likeNft(queryValues));
}else{
throw new Error('Unvalid authentication')
}
} catch (err) {
next(err)
}
}

async unlikeNft(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { cookie } = JSON.parse(req.body)
const queryValues = validationLikeUnlike({cookie, ...req.query})
if(decryptCookie(queryValues.cookie) === queryValues.walletId){
res.json(await NFTService.unlikeNft(queryValues));
}else{
throw new Error('Unvalid authentication')
}
} catch (err) {
next(err)
}
}
}

export default new Controller();
2 changes: 2 additions & 0 deletions src/api/controllers/nfts/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export default express
.get("/", controller.getNFTs)
.get("/history", controller.getHistory)
.get("/total-on-sale", controller.getTotalOnSale)
.post("/like", controller.likeNft)
.post("/unlike", controller.unlikeNft)
.get("/:id", controller.getNFT)
.get("/stat/:id", controller.getStatNFTsUser)
.get("/series/data", controller.getNFTsBySeries)
Expand Down
44 changes: 4 additions & 40 deletions 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, validationLikeUnlike, validationReviewRequested } from "../../validators/userValidators";
import { validationGetAccountBalance, validationGetUser, validationReviewRequested, validationGetUsers } from "../../validators/userValidators";

export class Controller {
async getUsers(
Expand All @@ -10,7 +10,9 @@ export class Controller {
next: NextFunction
): Promise<void> {
try {
res.redirect(`${TERNOA_API_URL}${req.originalUrl}`)
const queryValues = validationGetUsers({...req.params, ...req.query})
const users = await UserService.findUsers(queryValues, req.originalUrl);
res.json(users);
} catch (err) {
next(err);
}
Expand Down Expand Up @@ -86,44 +88,6 @@ export class Controller {
}
}

async likeNft(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { walletId } = req.query
const { cookie } = JSON.parse(req.body)
const queryValues = validationLikeUnlike({walletId, cookie})
if(decryptCookie(queryValues.cookie) === queryValues.walletId){
res.redirect(307, `${TERNOA_API_URL}${req.originalUrl}`)
}else{
throw new Error('Unvalid authentication')
}
} catch (err) {
next(err)
}
}

async unlikeNft(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { walletId } = req.query
const { cookie } = JSON.parse(req.body)
const queryValues = validationLikeUnlike({walletId, cookie})
if(decryptCookie(queryValues.cookie) === queryValues.walletId){
res.redirect(307, `${TERNOA_API_URL}${req.originalUrl}`)
}else{
throw new Error('Unvalid authentication')
}
} catch (err) {
next(err)
}
}

async verifyTwitter(
req: Request,
res: Response,
Expand Down
2 changes: 0 additions & 2 deletions src/api/controllers/users/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,4 @@ export default express
.get("/:id", controller.getUser) // ternoa-api
.get("/:id/caps", controller.getAccountBalance)
.post("/create", controller.newUser) // ternoa-api
.post("/like", controller.likeNft) // ternoa-api
.post("/unlike", controller.unlikeNft) // ternoa-api
.post("/:walletId", controller.updateUser); // ternoa-api
71 changes: 41 additions & 30 deletions src/api/services/nft.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { request } from "graphql-request";
import { AggregatePaginateResult } from 'mongoose'
import { DistinctNFTListResponse, INFT, NFTListResponse, CustomResponse, ISeries, INFTTransfer } from "../../interfaces/graphQL";
import fetch from "node-fetch";
import FollowModel from "../../models/follow";
import NftModel from "../../models/nft";
import NftViewModel from "../../models/nftView";
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 { canAddToSeriesQuery, addCategoriesNFTsQuery, getHistoryQuery, getSeriesStatusQuery, NFTBySeriesQuery, NFTQuery, NFTsQuery, statNFTsUserQuery, getTotalOnSaleQuery } from "../validators/nftValidators";
import { IUser } from "../../interfaces/IUser";
import { decryptCookie, TIME_BETWEEN_SAME_USER_VIEWS } from "../../utils";
import { canAddToSeriesQuery, addCategoriesNFTsQuery, getHistoryQuery, getSeriesStatusQuery, NFTBySeriesQuery, NFTQuery, NFTsQuery, statNFTsUserQuery, getTotalOnSaleQuery, likeUnlikeQuery } from "../validators/nftValidators";
import CategoryModel from "../../models/category";
import { ICategory } from "../../interfaces/ICategory";
// import { INFTLike } from "../../interfaces/INFTLike";
import L from "../../common/logger";
import { INftLike } from "src/interfaces/INftLikee";

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

Expand Down Expand Up @@ -47,30 +44,9 @@ export class NFTService {
}
// Liked only
if (query.filter?.liked) {
const data = await fetch(`${TERNOA_API_URL}/api/users/${query.filter.liked}?populateLikes=${true}`)
const user = await data.json() as IUser
query.filter.series = user.likedNFTs.length > 0 ? user.likedNFTs.map(x => x.serieId) : []
const likes = await NftLikeModel.find({walletId: query.filter.liked})
query.filter.series = likes.length > 0 ? likes.map(x => x.serieId) : []
}

// Sort mongo -> BLOCKED CAUSE OF DATASTRUCTURE
// const likesData: AggregatePaginateResult<{_id: string, count: number}> | null = null
/*if (query.sortMongo){
const sortArray = query.sortMongo.split(',')
const sortByLikes = sortArray.find(x => x.split(':')[0] === "likes")
if (sortByLikes){
const likesSort = sortByLikes.split(':')[1]
const likesResult = await fetch(`${TERNOA_API_URL}/api/nftLikes/&sort=${likesSort}`)
likesData = (await likesResult.json()).likesRanking
NFTs = NFTs.map(x => {
const likeAggregatedObject = likesData.docs.find(y => y._id === x.serieId)
const likesNumber = likeAggregatedObject ? likeAggregatedObject.count : 0
console.log(likesNumber)
return {...x, likesNumber}
})
.sort((a, b) => b.likesNumber - a.likesNumber)
}
}*/

// Indexer data
const gqlQuery = QueriesBuilder.NFTs(query);
const res: DistinctNFTListResponse = await request(indexerUrl, gqlQuery);
Expand Down Expand Up @@ -372,6 +348,41 @@ export class NFTService {
throw new Error("Count could not have been fetched");
}
}

/**
* Like an NFT
* @param query - see likeUnlikeQuery
* @throws Will throw an error if already liked or if db can't be reached
*/
async likeNft(query: likeUnlikeQuery): Promise<INftLike> {
try {
const data = {serieId: query.seriesId, walletId: query.walletId}
const nftLike = await NftLikeModel.findOne(data);
if (nftLike) throw new Error("NFT already liked")
const newLike = new NftLikeModel({nftId: query.nftId, serieId: query.seriesId, walletId: query.walletId})
await newLike.save()
return newLike
} catch (err) {
throw new Error("Couldn't like NFT");
}
}

/**
* Unlike an NFT
* @param query - see likeUnlikeQuery
* @throws Will throw an error if already liked or if db can't be reached
*/
async unlikeNft(query: likeUnlikeQuery): Promise<INftLike> {
try {
const data = {serieId: query.seriesId, walletId: query.walletId}
const nftLike = await NftLikeModel.findOne(data);
if (!nftLike) throw new Error("NFT already not liked")
await NftLikeModel.deleteOne(data)
return nftLike
} catch (err) {
throw new Error("Couldn't unlike NFT");
}
}
}

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

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


export class UserService {
/**
* Finds a users in DB
* @param query - see getUserQuery
* @throws Will throw an error if wallet ID doesn't exist
*/
async findUsers(
query: getUsersQuery,
originalUrl: string,
): Promise<CustomResponse<IUser>> {
try {
const res = await fetch(`${TERNOA_API_URL}${originalUrl}`)
if (!res.ok) throw new Error();
const response: CustomResponse<IUser> = await res.json();
if (query.populateLikes){
const likedNFTs = await NftLikeModel.find({walletId: {$in: response.data.map(x => x.walletId)}})
response.data.forEach(x => {
x.likedNFTs = likedNFTs.filter(y => y.walletId === x.walletId)
})
}
return response
} catch (err) {
throw new Error("Users can't be found : " + err);
}
}

/**
* Finds a user in DB
* @param query - see getUserQuery
Expand All @@ -21,10 +47,15 @@ export class UserService {
query: getUserQuery
): Promise<IUser> {
try {
const data = await fetch(`${TERNOA_API_URL}/api/users/${query.id}?populateLikes=${query.populateLikes ? query.populateLikes : false}`)
const data = await fetch(`${TERNOA_API_URL}/api/users/${query.id}`)
const user = await data.json() as IUser
let likedNFTs
let viewsCount = 0
if (!user || (user as any).errors?.length>0) throw new Error();
if (query.populateLikes){
likedNFTs = await NftLikeModel.find({walletId: query.id})
// user = {...((user as any)._doc as IUser), likedNFTs}
}
if (query.incViews){
const date = +new Date()
const views = await UserViewModel.find({viewed: query.id})
Expand All @@ -36,7 +67,7 @@ export class UserService {
viewsCount = views.length
}
}
return {...user, viewsCount};
return {...user, viewsCount, likedNFTs};
} catch (err) {
throw new Error("User can't be found " + err);
}
Expand Down
16 changes: 16 additions & 0 deletions src/api/validators/nftValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,19 @@ export const validationGetTotalOnSale = (query: any) => {
})
return validateQuery(validationSchema, query) as getTotalOnSaleQuery;
}

export type likeUnlikeQuery = {
walletId: string,
cookie: string,
nftId: string,
seriesId: string,
}
export const validationLikeUnlike = (query: any) => {
const validationSchema = Joi.object({
walletId: Joi.string().required(),
cookie: Joi.string().required(),
nftId: Joi.string().required(),
seriesId: Joi.string().required(),
});
return validateQuery(validationSchema, query) as likeUnlikeQuery;
};
52 changes: 35 additions & 17 deletions src/api/validators/userValidators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
import Joi from "joi";
import { LIMIT_MAX_PAGINATION } from "src/utils";
import { validateQuery } from ".";

export type getUsersQuery = {
populateLikes?: boolean
filter?: {
walletIds?: string[]
artist?: boolean
verified?: boolean
searchText?: string
},
pagination?: {
page?: number
limit?: number
}
}
export const validationGetUsers = (query: any) => {
let { pagination, filter } = query;
if (pagination) pagination = JSON.parse(pagination)
if (filter) filter = JSON.parse(filter)
const validationSchema = Joi.object({
populateLikes: Joi.boolean(),
filter: Joi.object({
walletIds: Joi.array().items(Joi.string()),
artist: Joi.boolean(),
verified: Joi.boolean(),
searchText: Joi.string()
}),
pagination: Joi.object({
page: Joi.number().integer().min(0),
limit: Joi.number().integer().min(0).max(LIMIT_MAX_PAGINATION),
}),
});
return validateQuery(validationSchema, { pagination, filter }) as getUsersQuery;
};

export type getUserQuery = {
id: string,
incViews?: boolean,
Expand Down Expand Up @@ -41,20 +75,4 @@ export const validationGetAccountBalance = (query: any) => {
id: Joi.string().required(),
});
return validateQuery(validationSchema, query) as getAccountBalanceQuery;
};


export type likeUnlikeQuery = {
walletId: string,
cookie: string,
}
export const validationLikeUnlike = (query: any) => {
const validationSchema = Joi.object({
walletId: Joi.string().required(),
cookie: Joi.string().required(),
});
return validateQuery(validationSchema, query) as likeUnlikeQuery;
};



};
Loading

0 comments on commit 81d557b

Please sign in to comment.