From c7f584c4e20eaddd26d3aacfedb9cd23434becc3 Mon Sep 17 00:00:00 2001 From: Behzad Rabiei Date: Wed, 18 Sep 2024 17:50:07 +0400 Subject: [PATCH] feat: finish the discourse network graph api --- src/config/index.ts | 12 +- src/controllers/discourse.controller.ts | 7 +- src/routes/v1/discourse.route.ts | 2 +- .../discourse/memberActivity.service.ts | 208 ++++++++++-------- src/services/memberActivity.service.ts | 3 - 5 files changed, 120 insertions(+), 112 deletions(-) diff --git a/src/config/index.ts b/src/config/index.ts index b7a23b5..f0706c7 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -68,12 +68,12 @@ export default { env: envVars.NODE_ENV, port: envVars.PORT, mongoose: { - // serverURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}/${envVars.DB_NAME}?authSource=admin`, - // botURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}?authSource=admin`, - // dbURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}?authSource=admin`, - serverURL: `mongodb://tcmongo:T0g3th3rCr3wM0ng0P55@3.122.238.21:37017/Core?directConnection=true`, - botURL: `mongodb://tcmongo:T0g3th3rCr3wM0ng0P55@3.122.238.21:37017/?directConnection=true`, - dbURL: `mongodb://tcmongo:T0g3th3rCr3wM0ng0P55@3.122.238.21:37017/?directConnection=true`, + serverURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}/${envVars.DB_NAME}?authSource=admin`, + botURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}?authSource=admin`, + dbURL: `mongodb://${envVars.DB_USER}:${envVars.DB_PASSWORD}@${envVars.DB_HOST}:${envVars.DB_PORT}?authSource=admin`, + // serverURL: `mongodb://tcmongo:T0g3th3rCr3wM0ng0P55@3.122.238.21:37017/Core?directConnection=true`, + // botURL: `mongodb://tcmongo:T0g3th3rCr3wM0ng0P55@3.122.238.21:37017/?directConnection=true`, + // dbURL: `mongodb://tcmongo:T0g3th3rCr3wM0ng0P55@3.122.238.21:37017/?directConnection=true`, }, redis: { host: envVars.REDIS_HOST, diff --git a/src/controllers/discourse.controller.ts b/src/controllers/discourse.controller.ts index 882a218..825ef8e 100644 --- a/src/controllers/discourse.controller.ts +++ b/src/controllers/discourse.controller.ts @@ -36,12 +36,9 @@ const lineGraph = catchAsync(async function (req: IAuthAndPlatform, res: Respons }); const membersInteractionsNetworkGraph = catchAsync(async function (req: IAuthAndPlatform, res: Response) { - const platform = await platformService.getPlatformById(new Types.ObjectId(req.params.platformId)); - console.log(platform); - const networkGraphData = await discourseService.memberActivityService.getMembersInteractionsNetworkGraph( - platform?.id, - platform?.name as SupportedNeo4jPlatforms, + req.platform.id, + req.platform?.name as SupportedNeo4jPlatforms, ); res.send(networkGraphData); }); diff --git a/src/routes/v1/discourse.route.ts b/src/routes/v1/discourse.route.ts index c3e0bba..93d1225 100644 --- a/src/routes/v1/discourse.route.ts +++ b/src/routes/v1/discourse.route.ts @@ -21,7 +21,7 @@ router.post( router.post( '/member-activity/:platformId/members-interactions-network-graph', - // auth('admin', 'view'), + auth('admin', 'view'), validate(discourseValidation.membersInteractionsNetworkGraph), discourseController.membersInteractionsNetworkGraph, ); diff --git a/src/services/discourse/memberActivity.service.ts b/src/services/discourse/memberActivity.service.ts index 76491b0..a1df73a 100644 --- a/src/services/discourse/memberActivity.service.ts +++ b/src/services/discourse/memberActivity.service.ts @@ -4,7 +4,8 @@ import parentLogger from '../../config/logger'; import { NEO4J_PLATFORM_INFO } from '../../constants/neo4j.constant'; import { SupportedNeo4jPlatforms } from '../../types/neo4j.type'; import { DatabaseManager } from '@togethercrew.dev/db'; - +import { ApiError } from '../../utils'; +import httpStatus from 'http-status'; const logger = parentLogger.child({ module: 'DiscourseMemberActivityService' }); type networkGraphUserInformationType = { @@ -14,13 +15,22 @@ type networkGraphUserInformationType = { roles: []; ngu: string; }; + +function getNgu(user: any): string { + if (user.options.name !== '') { + return user.options.name; + } else { + return user.options.username; + } +} + function getUserInformationForNetworkGraph(user: any): networkGraphUserInformationType { return { username: user.options.username, avatar: user.options.avatar, joinedAt: user.joined_at, roles: [], - ngu: user.options.name, + ngu: getNgu(user), }; } @@ -30,115 +40,119 @@ async function getMembersInteractionsNetworkGraph( platformId: string, platformName: SupportedNeo4jPlatforms, ): Promise { - const platformConnection = await DatabaseManager.getInstance().getPlatformDb(platformId); - const usersInNetworkGraph: string[] = []; - console.log(platformId, NEO4J_PLATFORM_INFO[platformName].member, NEO4J_PLATFORM_INFO[platformName].member); - // userInteraction - const usersInteractionsQuery = ` + try { + const platformConnection = await DatabaseManager.getInstance().getPlatformDb(platformId); + const usersInNetworkGraph: string[] = []; + // userInteraction + const usersInteractionsQuery = ` MATCH () -[r:INTERACTED_WITH {platformId: "${platformId}"}]-() WITH max(r.date) as latest_date MATCH (a:${NEO4J_PLATFORM_INFO[platformName].member})-[r:INTERACTED_WITH {platformId: "${platformId}", date: latest_date}]->(b:${NEO4J_PLATFORM_INFO[platformName].member}) RETURN a, r, b`; - console.log(usersInteractionsQuery); - const neo4jUsersInteractionsData = await Neo4j.read(usersInteractionsQuery); - const { records: neo4jUsersInteractions } = neo4jUsersInteractionsData; - const usersInteractions = neo4jUsersInteractions.map((usersInteraction) => { - // @ts-ignore - const { _fieldLookup, _fields } = usersInteraction; - const a = _fields[_fieldLookup['a']]; - const r = _fields[_fieldLookup['r']]; - const b = _fields[_fieldLookup['b']]; - - const aUserId = a?.properties?.id as string; - const rWeeklyInteraction = r?.properties?.weight as number; - const bUserId = b?.properties?.id as string; - - usersInNetworkGraph.push(aUserId); - usersInNetworkGraph.push(bUserId); - const interaction = { - aUserId, - bUserId, - rWeeklyInteraction, - }; - - return interaction; - }); - console.log('usersInteractions', usersInteractions); - // userRadius - const userRadiusQuery = ` + const neo4jUsersInteractionsData = await Neo4j.read(usersInteractionsQuery); + const { records: neo4jUsersInteractions } = neo4jUsersInteractionsData; + const usersInteractions = neo4jUsersInteractions.map((usersInteraction) => { + // @ts-ignore + const { _fieldLookup, _fields } = usersInteraction; + const a = _fields[_fieldLookup['a']]; + const r = _fields[_fieldLookup['r']]; + const b = _fields[_fieldLookup['b']]; + + const aUserId = a?.properties?.id as string; + const rWeeklyInteraction = r?.properties?.weight as number; + const bUserId = b?.properties?.id as string; + + usersInNetworkGraph.push(aUserId); + usersInNetworkGraph.push(bUserId); + const interaction = { + aUserId, + bUserId, + rWeeklyInteraction, + }; + + return interaction; + }); + + // userRadius + const userRadiusQuery = ` MATCH () -[r:INTERACTED_WITH {platformId: "${platformId}"}]-() WITH max(r.date) as latest_date MATCH (a:${NEO4J_PLATFORM_INFO[platformName].member}) -[r:INTERACTED_WITH {date: latest_date, platformId :"${platformId}"}]-(:${NEO4J_PLATFORM_INFO[platformName].member}) WITH a, r RETURN a.id as userId, SUM(r.weight) as radius`; - const neo4jUserRadiusData = await Neo4j.read(userRadiusQuery); - const { records: neo4jUserRadius } = neo4jUserRadiusData; - const userRadius = neo4jUserRadius.map((userRadius) => { - // @ts-ignore - const { _fieldLookup, _fields } = userRadius; - const userId = _fields[_fieldLookup['userId']] as string; - const radius = _fields[_fieldLookup['radius']] as number; - - return { userId, radius }; - }); - // userStatus - const userStatusQuery = ` + const neo4jUserRadiusData = await Neo4j.read(userRadiusQuery); + const { records: neo4jUserRadius } = neo4jUserRadiusData; + const userRadius = neo4jUserRadius.map((userRadius) => { + // @ts-ignore + const { _fieldLookup, _fields } = userRadius; + const userId = _fields[_fieldLookup['userId']] as string; + const radius = _fields[_fieldLookup['radius']] as number; + + return { userId, radius }; + }); + // userStatus + const userStatusQuery = ` MATCH () -[r:INTERACTED_IN]-(g:${NEO4J_PLATFORM_INFO[platformName].platform} {id: "${platformId}"}) WITH max(r.date) as latest_date MATCH (a:${NEO4J_PLATFORM_INFO[platformName].member})-[r:INTERACTED_IN {date: latest_date}]->(g:${NEO4J_PLATFORM_INFO[platformName].platform} {id: "${platformId}"}) RETURN a.id as userId, r.status as status`; - const neo4jUserStatusData = await Neo4j.read(userStatusQuery); - const { records: neo4jUserStatus } = neo4jUserStatusData; - const userStatus = neo4jUserStatus.map((userStatus) => { - // @ts-ignore - const { _fieldLookup, _fields } = userStatus; - const userId = _fields[_fieldLookup['userId']] as string; - const status = _fields[_fieldLookup['status']] as number; - const stats = - status == 0 ? NodeStats.SENDER : status == 1 ? NodeStats.RECEIVER : status == 2 ? NodeStats.BALANCED : null; - - return { userId, stats }; - }); - - console.log('usersInNetworkGraph', usersInNetworkGraph); - // usersInfo - const usersInfo = await platformConnection.models.rawmembers.find({ id: { $in: usersInNetworkGraph } }); - console.log(usersInfo); - - // prepare data - const response = usersInteractions.flatMap((interaction) => { - const { aUserId, bUserId, rWeeklyInteraction } = interaction; - // Radius - const aUserRadiusObj = userRadius.find((userRadius) => userRadius.userId == aUserId); - const aUserRadius = aUserRadiusObj?.radius as number; - const bUserRadiusObj = userRadius.find((userRadius) => userRadius.userId == bUserId); - const bUserRadius = bUserRadiusObj?.radius as number; - // Status - const aUserStatsObj = userStatus.find((userStatus) => userStatus.userId == aUserId); - const aUserStats = aUserStatsObj?.stats; - const bUserStatsObj = userStatus.find((userStatus) => userStatus.userId == bUserId); - const bUserStats = bUserStatsObj?.stats; - // userInfo - const aUser = usersInfo.find((user) => user.discordId === aUserId); - const bUser = usersInfo.find((user) => user.discordId === bUserId); - if (!aUser || !bUser) return []; - - const aInfo = getUserInformationForNetworkGraph(aUser); - const bInfo = getUserInformationForNetworkGraph(bUser); - - if (!aUserStats || !bUserStats) { - return []; - } - - return { - from: { id: aUserId, radius: aUserRadius, stats: aUserStats, ...aInfo }, - to: { id: bUserId, radius: bUserRadius, stats: bUserStats, ...bInfo }, - width: rWeeklyInteraction, - }; - }); - - return response; + const neo4jUserStatusData = await Neo4j.read(userStatusQuery); + const { records: neo4jUserStatus } = neo4jUserStatusData; + const userStatus = neo4jUserStatus.map((userStatus) => { + // @ts-ignore + const { _fieldLookup, _fields } = userStatus; + const userId = _fields[_fieldLookup['userId']] as string; + const status = _fields[_fieldLookup['status']] as number; + const stats = + status == 0 ? NodeStats.SENDER : status == 1 ? NodeStats.RECEIVER : status == 2 ? NodeStats.BALANCED : null; + + return { userId, stats }; + }); + + const usersInfo = await platformConnection.db + .collection('rawmembers') + .find({ id: { $in: usersInNetworkGraph } }) + .toArray(); + + // prepare data + const response = usersInteractions.flatMap((interaction) => { + const { aUserId, bUserId, rWeeklyInteraction } = interaction; + // Radius + const aUserRadiusObj = userRadius.find((userRadius) => userRadius.userId == aUserId); + const aUserRadius = aUserRadiusObj?.radius as number; + const bUserRadiusObj = userRadius.find((userRadius) => userRadius.userId == bUserId); + const bUserRadius = bUserRadiusObj?.radius as number; + // Status + const aUserStatsObj = userStatus.find((userStatus) => userStatus.userId == aUserId); + const aUserStats = aUserStatsObj?.stats; + const bUserStatsObj = userStatus.find((userStatus) => userStatus.userId == bUserId); + const bUserStats = bUserStatsObj?.stats; + + // userInfo + const aUser = usersInfo.find((user) => user.id === aUserId); + const bUser = usersInfo.find((user) => user.id === bUserId); + if (!aUser || !bUser) return []; + + const aInfo = getUserInformationForNetworkGraph(aUser); + const bInfo = getUserInformationForNetworkGraph(bUser); + + if (!aUserStats || !bUserStats) { + return []; + } + + return { + from: { id: aUserId, radius: aUserRadius, stats: aUserStats, ...aInfo }, + to: { id: bUserId, radius: bUserRadius, stats: bUserStats, ...bInfo }, + width: rWeeklyInteraction, + }; + }); + + return response; + } catch (error) { + logger.error(error, 'Failed to get discourse members interaction network graph'); + throw new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Failed to get discourse members interaction network graph'); + } } export default { diff --git a/src/services/memberActivity.service.ts b/src/services/memberActivity.service.ts index d1fd0ef..cb0782b 100644 --- a/src/services/memberActivity.service.ts +++ b/src/services/memberActivity.service.ts @@ -910,7 +910,6 @@ function getActivityComposition(guildMember: IGuildMember, memberActivity: any, } type networkGraphUserInformationType = { - discordId: Snowflake; username: string; avatar: string | null | undefined; joinedAt: Date | null; @@ -918,7 +917,6 @@ type networkGraphUserInformationType = { ngu: string; }; function getUserInformationForNetworkGraph(user: IGuildMember, guildRoles: IRole[]): networkGraphUserInformationType { - const discordId = user?.discordId; const fullUsername = guildMemberService.getUsername(user); const avatar = user?.avatar; const joinedAt = user?.joinedAt; @@ -926,7 +924,6 @@ function getUserInformationForNetworkGraph(user: IGuildMember, guildRoles: IRole const ngu = guildMemberService.getNgu(user); return { - discordId, username: fullUsername, avatar, joinedAt,