diff --git a/src/controllers/nft.controller.ts b/src/controllers/nft.controller.ts index d284d68..47197fe 100644 --- a/src/controllers/nft.controller.ts +++ b/src/controllers/nft.controller.ts @@ -1,102 +1,17 @@ import { Request, Response } from 'express'; import { catchAsync } from '../utils'; import parentLogger from '../config/logger'; -import { moduleService, platformService, ociService } from '../services'; -import { ApiError } from '../utils'; -import { IModule, IPlatform } from '@togethercrew.dev/db'; -import { HydratedDocument } from 'mongoose'; -import * as Neo4j from '../neo4j'; -import { NEO4J_PLATFORM_INFO } from '../constants/neo4j.constant'; -import { SupportedNeo4jPlatforms } from '../types/neo4j.type'; +import { nftService } from '../services'; const logger = parentLogger.child({ module: 'NftController' }); const getReputationScore = catchAsync(async function (req: Request, res: Response) { const { tokenId, address } = req.params; - const supportedPlatforms = ['discord', 'discourse']; - - let repuationScore; logger.debug(tokenId, address); - const profiles: Array = await getProfilesOnAllSupportedChains(address); - logger.debug(profiles); - const dynamicNftModule = await moduleService.getModuleByFilter({ 'options.platforms.0.metadata.tokenId': tokenId }); - logger.debug(dynamicNftModule); - - for (let i = 0; i < supportedPlatforms.length; i++) { - const platform = await platformService.getPlatformByFilter({ - name: supportedPlatforms[i], - community: dynamicNftModule?.community, - }); - logger.debug({ i, platform, supportedPlatforms: supportedPlatforms[i] }); - for (let j = 0; j < profiles.length; j++) { - const profile = profiles[j]; - logger.debug({ i, j, profile, supportedPlatforms: supportedPlatforms[i] }); - const temp = platform?.name as SupportedNeo4jPlatforms; - if (profile.profile.provider === supportedPlatforms[i]) { - const reputationScoreQuery = ` - MATCH (:${NEO4J_PLATFORM_INFO[temp].member} {id: "${profile.profile.id}"})-[r:HAVE_METRICS {platformId: "${platform?.id}"}]->(a) - WITH r.date as metrics_date, r.closenessCentrality as memberScore - ORDER BY metrics_date DESC - LIMIT 1 - MATCH (user:${NEO4J_PLATFORM_INFO[temp].member})-[user_r:HAVE_METRICS {platformId: "${platform?.id}", date: metrics_date}]->(user) - WITH memberScore, MAX(user_r.closenessCentrality) as maxScore - RETURN memberScore / maxScore AS reputation_score - `; - - const neo4jData = await Neo4j.read(reputationScoreQuery); - const { records } = neo4jData; - logger.debug(records); - - const reputationScoreResponse = records[0]; - - logger.debug(reputationScoreResponse); - - const { _fieldLookup, _fields } = reputationScoreResponse as unknown as { - _fieldLookup: Record; - _fields: number[]; - }; - - repuationScore = _fields[_fieldLookup['reputation_score']]; - logger.debug(repuationScore); - } - } - } - return repuationScore; + const reputationScore = await nftService.getReputationScore(tokenId, address); + res.send(reputationScore); }); -async function getProfilesOnAllSupportedChains(address: string) { - let profiles: Array = []; - const supportedChainIds = [11155111]; - for (let i = 0; i < supportedChainIds.length; i++) { - const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]); - profiles = profiles.concat(chainProfiles); - } - return profiles; -} - -function shouldProfilesExist(profiles: Array) { - if (profiles.length < 0) { - throw new ApiError(400, 'User has no any onchain profiles'); - } -} - -function shouldDynamicNftModuleExist(dynamicNftModule: HydratedDocument | null) { - if (!dynamicNftModule) { - throw new ApiError(400, "There's not any assoicated dynamic nft module to the token Id"); - } -} - -function shouldPlatformExist(platform: HydratedDocument | null) { - if (!platform) { - throw new ApiError(400, "There's not any platform connected for requested platform"); - } -} - -function shouldProfileExist(profile: any) { - if (!profile) { - throw new ApiError(400, "There's not any user oncahin profile for requested platform"); - } -} export default { getReputationScore, }; diff --git a/src/routes/v1/nft.route.ts b/src/routes/v1/nft.route.ts index c1a4b0f..7fc008b 100644 --- a/src/routes/v1/nft.route.ts +++ b/src/routes/v1/nft.route.ts @@ -5,7 +5,7 @@ import { validate } from '../../middlewares'; const router = express.Router(); // Routes -router.post( +router.get( '/:tokenId/:address/reputation-score', validate(nftValidation.getReputationScore), nftController.getReputationScore, diff --git a/src/services/index.ts b/src/services/index.ts index d2d1cd5..401f7d2 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -15,6 +15,8 @@ import githubService from './github'; import notionService from './notion'; import discourseService from './discourse'; import ociService from './oci.service'; +import nftService from './nft.service'; + export { userService, authService, @@ -33,4 +35,5 @@ export { notionService, discourseService, ociService, + nftService, }; diff --git a/src/services/nft.service.ts b/src/services/nft.service.ts index 01afec2..3ea4d11 100644 --- a/src/services/nft.service.ts +++ b/src/services/nft.service.ts @@ -1,35 +1,112 @@ import { HydratedDocument, Types } from 'mongoose'; import httpStatus from 'http-status'; -import { Platform, IPlatform } from '@togethercrew.dev/db'; +import { IPlatform, IModule, PlatformNames } from '@togethercrew.dev/db'; import ApiError from '../utils/ApiError'; -import sagaService from './saga.service'; -import discourseService from './discourse'; -import { Snowflake } from 'discord.js'; -import { analyzerAction, analyzerWindow } from '../config/analyzer.statics'; -import { PlatformNames } from '@togethercrew.dev/db'; +import * as Neo4j from '../neo4j'; +import { NEO4J_PLATFORM_INFO } from '../constants/neo4j.constant'; +import { SupportedNeo4jPlatforms } from '../types/neo4j.type'; +import parentLogger from '../config/logger'; +import moduleService from './module.service'; +import platformService from './platform.service'; +import ociService from './oci.service'; + +const logger = parentLogger.child({ module: 'NftService' }); + +const supportedPlatforms = [PlatformNames.Discord, PlatformNames.Discourse]; /** * get reputation score * @param {IPlatform} PlatformBody * @returns {Promise>} */ -const getReputationScore = async (PlatformBody: IPlatform): Promise> => { - if (PlatformBody.name === PlatformNames.Discord || PlatformBody.name === PlatformNames.Discourse) { - if (PlatformBody.metadata) { - PlatformBody.metadata = { - action: analyzerAction, - window: analyzerWindow, - ...PlatformBody.metadata, - }; +const getReputationScore = async (tokenId: string, address: string) => { + const dynamicNftModule = await moduleService.getModuleByFilter({ 'options.platforms.0.metadata.tokenId': tokenId }); + logger.debug(dynamicNftModule); + throwErrorIfDynamicNftModuleDoesNotExist(dynamicNftModule); + + const profiles: Array = await getProfiles(address); + logger.debug(profiles); + throwErrorIfUserHasNoOnChainProfiles(profiles); + + let reputationScore = 0; + for (const profile of profiles) { + if (supportedPlatforms.includes(profile.profile.provider)) { + const platform = await platformService.getPlatformByFilter({ + name: profile.profile.provider, + community: dynamicNftModule?.community, + }); + if (platform) { + reputationScore = (await calculateReputationScoreForProfile(profile, platform)) + reputationScore; + logger.debug(`Reputation Score: ${reputationScore}`); + } } } - const platform = await Platform.create(PlatformBody); - if (PlatformBody.name === PlatformNames.Discord) { - await sagaService.createAndStartFetchMemberSaga(platform._id); - } - return platform; + return { + reputationScore, + communintyId: dynamicNftModule?.id, + }; }; +async function getProfiles(address: string) { + let profiles: Array = []; + const supportedChainIds = [11155111]; + for (let i = 0; i < supportedChainIds.length; i++) { + const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]); + profiles = profiles.concat(chainProfiles); + } + return profiles; +} + +async function calculateReputationScoreForProfile(profile: any, platform: any): Promise { + const platformName = platform.name as SupportedNeo4jPlatforms; + const memberLabel = NEO4J_PLATFORM_INFO[platformName].member; + const platformId = platform.id; + const profileId = profile.profile.id; + + const reputationScoreQuery = buildReputationScoreQuery(profileId, platformId, memberLabel); + const neo4jData = await Neo4j.read(reputationScoreQuery); + + return extractReputationScoreFromNeo4jData(neo4jData); +} + +function buildReputationScoreQuery(profileId: string, platformId: string, memberLabel: string): string { + return ` + MATCH (:${memberLabel} {id: "${profileId}"})-[r:HAVE_METRICS {platformId: "${platformId}"}]->(a) + WITH r.date as metrics_date, r.closenessCentrality as memberScore + ORDER BY metrics_date DESC + LIMIT 1 + MATCH (user:${memberLabel})-[user_r:HAVE_METRICS {platformId: "${platformId}", date: metrics_date}]->(user) + WITH memberScore, MAX(user_r.closenessCentrality) as maxScore + RETURN memberScore / maxScore AS reputation_score + `; +} + +function extractReputationScoreFromNeo4jData(neo4jData: any): number { + const { records } = neo4jData; + logger.debug(`Neo4j Records: ${JSON.stringify(records)}`); + + if (records.length === 0) { + return 0; + } + + const reputationScoreResponse = records[0]; + const reputationScore = reputationScoreResponse.get('reputation_score'); + + return reputationScore || 0; +} + +function throwErrorIfUserHasNoOnChainProfiles(profiles: Array) { + if (profiles.length === 0) { + throw new ApiError(400, 'User does not have any on-chain profiles.'); + } +} + +function throwErrorIfDynamicNftModuleDoesNotExist(dynamicNftModule: HydratedDocument | null) { + if (!dynamicNftModule) { + throw new ApiError(400, 'There is no associated dynamic NFT module for the provided token ID.'); + } +} + export default { getReputationScore, };