Skip to content

Commit

Permalink
feat: add getReputationScore endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Behzad-rabiei committed Nov 14, 2024
1 parent 0e7309d commit 4a38975
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const envVarsSchema = Joi.object()
REDIS_PORT: Joi.string().required().description('Redis port'),
REDIS_PASSWORD: Joi.string().required().description('Reids password').allow(''),
DISCOURSE_EXTRACTION_URL: Joi.string().required().description('Discourse extraction url'),
OCI_BACKEND_URl: Joi.string().required().description('Oci Backend url'),
})
.unknown();

Expand Down Expand Up @@ -158,4 +159,5 @@ export default {
discourse: {
extractionURL: envVars.DISCOURSE_EXTRACTION_URL,
},
ociBackendURL: envVars.OCI_BACKEND_URl,
};
3 changes: 3 additions & 0 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import announcementController from './announcement.controller';
import categoryController from './category.controller';
import moduleController from './module.controller';
import discourseController from './discourse.controller';
import nftController from './nft.controller';

export {
authController,
userController,
Expand All @@ -21,4 +23,5 @@ export {
categoryController,
moduleController,
discourseController,
nftController,
};
109 changes: 109 additions & 0 deletions src/controllers/nft.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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';

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<any> = 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] });
// shouldPlatformExist(platform);
for (let j = 0; j < profiles.length; j++) {
const profile = profiles[j];
logger.debug({ i, j, profile, supportedPlatforms: supportedPlatforms[i] });
if (profile.profile.provider === supportedPlatforms[i]) {
const reputationScoreQuery = `
MATCH (:${NEO4J_PLATFORM_INFO[platform?.name as SupportedNeo4jPlatforms].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[platform?.name as SupportedNeo4jPlatforms].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<string, number>;
_fields: number[];
};

repuationScore = _fields[_fieldLookup['reputation_score']];
logger.debug(repuationScore);
}
}
}
return repuationScore;
});

async function getProfilesOnAllSupportedChains(address: string) {
let profiles: Array<any> = [];
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<any>) {
if (profiles.length < 0) {
throw new ApiError(400, 'User has no any onchain profiles');
}
}

function shouldDynamicNftModuleExist(dynamicNftModule: HydratedDocument<IModule> | null) {
if (!dynamicNftModule) {
throw new ApiError(400, "There's not any assoicated dynamic nft module to the token Id");
}
}

function shouldPlatformExist(platform: HydratedDocument<IPlatform> | 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,
};

// const platform = await platformService.getPlatformByFilter({
// name: platforms[i],
// community: dynamicNftModule?.community,
// });
// const profile = profiles.find((p: any) => p.profile.provider === platform);
// // Do the Cypher || N/A ?? || drop-down for platforms
5 changes: 5 additions & 0 deletions src/routes/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import announcementRoute from './announcement.route';
import categoryRoute from './category.route';
import moduleRoute from './module.route';
import discourseRoute from './discourse.route';
import nftRoute from './nft.route';
const router = express.Router();

const defaultRoutes = [
Expand Down Expand Up @@ -63,6 +64,10 @@ const defaultRoutes = [
path: '/discourse',
route: discourseRoute,
},
{
path: '/nft',
route: nftRoute,
},
];

defaultRoutes.forEach((route) => {
Expand Down
14 changes: 14 additions & 0 deletions src/routes/v1/nft.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import express from 'express';
import { nftController } from '../../controllers';
import { nftValidation } from '../../validations';
import { validate } from '../../middlewares';
const router = express.Router();

// Routes
router.post(
'/:tokenId/:address/reputation-score',
validate(nftValidation.getReputationScore),
nftController.getReputationScore,
);

export default router;
2 changes: 2 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import googleService from './google';
import githubService from './github';
import notionService from './notion';
import discourseService from './discourse';
import ociService from './oci.service';
export {
userService,
authService,
Expand All @@ -31,4 +32,5 @@ export {
githubService,
notionService,
discourseService,
ociService,
};
35 changes: 35 additions & 0 deletions src/services/nft.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { HydratedDocument, Types } from 'mongoose';
import httpStatus from 'http-status';
import { Platform, IPlatform } 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';

/**
* get reputation score
* @param {IPlatform} PlatformBody
* @returns {Promise<HydratedDocument<IPlatform>>}
*/
const getReputationScore = async (PlatformBody: IPlatform): Promise<HydratedDocument<IPlatform>> => {
if (PlatformBody.name === PlatformNames.Discord || PlatformBody.name === PlatformNames.Discourse) {
if (PlatformBody.metadata) {
PlatformBody.metadata = {
action: analyzerAction,
window: analyzerWindow,
...PlatformBody.metadata,
};
}
}
const platform = await Platform.create(PlatformBody);
if (PlatformBody.name === PlatformNames.Discord) {
await sagaService.createAndStartFetchMemberSaga(platform._id);
}
return platform;
};

export default {
getReputationScore,
};
29 changes: 29 additions & 0 deletions src/services/oci.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import fetch from 'node-fetch';
import config from '../config';
import { ApiError } from '../utils';
import parentLogger from '../config/logger';

const logger = parentLogger.child({ module: 'OciService' });

async function getProfiles(address: string, chainId: number) {
try {
console.log(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${address}`);
const response = await fetch(`${config.ociBackendURL}/api/v1/oci/profiles/${chainId}/${address}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
if (response.ok) {
return await response.json();
} else {
const errorResponse = await response.text();
throw new Error(errorResponse);
}
} catch (error: any) {
logger.error(error, 'Failed to get profiles from oci backend');
throw new ApiError(590, 'Failed to get profiles from oci backend ');
}
}

export default {
getProfiles,
};
6 changes: 3 additions & 3 deletions src/services/platform.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HydratedDocument, Types } from 'mongoose';
import { HydratedDocument, Types, FilterQuery } from 'mongoose';
import httpStatus from 'http-status';
import { Platform, IPlatform } from '@togethercrew.dev/db';
import ApiError from '../utils/ApiError';
Expand Down Expand Up @@ -38,7 +38,7 @@ const createPlatform = async (PlatformBody: IPlatform): Promise<HydratedDocument
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
*/
const queryPlatforms = async (filter: object, options: object) => {
const queryPlatforms = async (filter: FilterQuery<IPlatform>, options: object) => {
return Platform.paginate(filter, options);
};

Expand All @@ -47,7 +47,7 @@ const queryPlatforms = async (filter: object, options: object) => {
* @param {Object} filter - Mongo filter
* @returns {Promise<HydratedDocument<IPlatform> | null>}
*/
const getPlatformByFilter = async (filter: object): Promise<HydratedDocument<IPlatform> | null> => {
const getPlatformByFilter = async (filter: FilterQuery<IPlatform>): Promise<HydratedDocument<IPlatform> | null> => {
return Platform.findOne(filter);
};

Expand Down
3 changes: 3 additions & 0 deletions src/validations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import platformValidation from './platform.validation';
import announcementValidation from './announcement.validation';
import moduleValidation from './module.validation';
import discourseValidation from './discourse.validation';
import nftValidation from './nft.validation';

export {
authValidation,
guildValidation,
Expand All @@ -19,4 +21,5 @@ export {
announcementValidation,
moduleValidation,
discourseValidation,
nftValidation,
};
15 changes: 15 additions & 0 deletions src/validations/nft.validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Joi from 'joi';
import { PlatformNames } from '@togethercrew.dev/db';

const getReputationScore = {
params: Joi.object().keys({
tokenId: Joi.string().required(),
address: Joi.string()
.regex(/^0x[a-fA-F0-9]{40}$/)
.required(),
}),
};

export default {
getReputationScore,
};

0 comments on commit 4a38975

Please sign in to comment.