-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: fix bugs #408
fix: fix bugs #408
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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] }); | ||
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<string, number>; | ||
_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<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, | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -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<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 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<any> = 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; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix reputation score calculation logic The current implementation adds reputation scores together without averaging, which could lead to inflated scores for users with multiple profiles. Apply this fix: - reputationScore = (await calculateReputationScoreForProfile(profile, platform)) + reputationScore;
+ const profileScore = await calculateReputationScoreForProfile(profile, platform);
+ reputationScore = Math.max(reputationScore, profileScore); // Take highest score across platforms 📝 Committable suggestion
Suggested change
|
||||||||
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, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typo in property key 'communintyId' The property key Apply this diff to correct the typo: return {
reputationScore,
- communintyId: dynamicNftModule?.id,
+ communityId: dynamicNftModule?.id,
};
|
||||||||
}; | ||||||||
}; | ||||||||
|
||||||||
async function getProfiles(address: string) { | ||||||||
let profiles: Array<any> = []; | ||||||||
const supportedChainIds = [11155111]; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Move chain ID to configuration The supported chain ID is hard-coded. This should be moved to a configuration file for better maintainability. Create a config file and move the chain IDs there: // src/config/blockchain.config.ts
export const SUPPORTED_CHAIN_IDS = {
SEPOLIA: 11155111,
// Add other chains as needed
}; |
||||||||
for (let i = 0; i < supportedChainIds.length; i++) { | ||||||||
const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]); | ||||||||
profiles = profiles.concat(chainProfiles); | ||||||||
Comment on lines
+54
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for OCI service calls Exceptions from Example implementation: for (let i = 0; i < supportedChainIds.length; i++) {
- const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]);
- profiles = profiles.concat(chainProfiles);
+ try {
+ const chainProfiles = await ociService.getProfiles(address, supportedChainIds[i]);
+ profiles = profiles.concat(chainProfiles);
+ } catch (error) {
+ logger.error(`Error fetching profiles for chain ID ${supportedChainIds[i]}: ${error.message}`);
+ // Optionally, handle the error or continue to the next iteration
+ }
}
|
||||||||
} | ||||||||
return profiles; | ||||||||
} | ||||||||
|
||||||||
async function calculateReputationScoreForProfile(profile: any, platform: any): Promise<number> { | ||||||||
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 | ||||||||
`; | ||||||||
} | ||||||||
Comment on lines
+72
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Prevent potential Cypher injection by using parameterized queries The Refactor the code to use parameters: -function buildReputationScoreQuery(profileId: string, platformId: string, memberLabel: string): string {
- return `
- MATCH (:${memberLabel} {id: "${profileId}"})-[r:HAVE_METRICS {platformId: "${platformId}"}]->(a)
+function buildReputationScoreQuery(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)
+ 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
`;
} When executing the query, pass parameters separately: async function calculateReputationScoreForProfile(profile: any, platform: any): Promise<number> {
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);
+ const reputationScoreQuery = buildReputationScoreQuery(memberLabel);
+ const params = { profileId, platformId };
+ const neo4jData = await Neo4j.run(reputationScoreQuery, params);
return extractReputationScoreFromNeo4jData(neo4jData);
} Ensure your Neo4j driver supports parameterized queries. |
||||||||
|
||||||||
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<any>) { | ||||||||
if (profiles.length === 0) { | ||||||||
throw new ApiError(400, 'User does not have any on-chain profiles.'); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
function throwErrorIfDynamicNftModuleDoesNotExist(dynamicNftModule: HydratedDocument<IModule> | null) { | ||||||||
if (!dynamicNftModule) { | ||||||||
throw new ApiError(400, 'There is no associated dynamic NFT module for the provided token ID.'); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
export default { | ||||||||
getReputationScore, | ||||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add input validation and error handling
The simplified implementation could benefit from some essential safeguards:
tokenId
andaddress
Consider implementing these improvements:
📝 Committable suggestion