Skip to content

Commit

Permalink
feat: working on the discourse network graph api
Browse files Browse the repository at this point in the history
  • Loading branch information
Behzad-rabiei committed Sep 17, 2024
1 parent e32649e commit 4925ab9
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 9 deletions.
9 changes: 6 additions & 3 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +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://${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:[email protected]:37017/Core?directConnection=true`,
botURL: `mongodb://tcmongo:[email protected]:37017/?directConnection=true`,
dbURL: `mongodb://tcmongo:[email protected]:37017/?directConnection=true`,
},
redis: {
host: envVars.REDIS_HOST,
Expand Down
21 changes: 20 additions & 1 deletion src/controllers/discourse.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import { Response } from 'express';
import { discourseService } from '../services';
import { IAuthAndPlatform } from '../interfaces/Request.interface';
import { catchAsync, date, charts } from '../utils';
import { DatabaseManager } from '@togethercrew.dev/db';
import { DatabaseManager, Platform } from '@togethercrew.dev/db';
import moment from 'moment-timezone';
import { SupportedNeo4jPlatforms } from '../types/neo4j.type';
import passport, { use } from 'passport';
import httpStatus from 'http-status';
import { ApiError, roleUtil, pick } from '../utils';
import { communityService, platformService, moduleService } from '../services';
import { Types } from 'mongoose';
import { UserRole } from '../interfaces';

const heatmapChart = catchAsync(async function (req: IAuthAndPlatform, res: Response) {
const platformConnection = await DatabaseManager.getInstance().getPlatformDb(req.platform?.id);
Expand All @@ -28,7 +35,19 @@ const lineGraph = catchAsync(async function (req: IAuthAndPlatform, res: Respons
res.send(lineGraph);
});

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,
);
res.send(networkGraphData);
});

export default {
heatmapChart,
lineGraph,
membersInteractionsNetworkGraph,
};
7 changes: 7 additions & 0 deletions src/routes/v1/discourse.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,11 @@ router.post(
discourseController.lineGraph,
);

router.post(
'/member-activity/:platformId/members-interactions-network-graph',
// auth('admin', 'view'),
validate(discourseValidation.membersInteractionsNetworkGraph),
discourseController.membersInteractionsNetworkGraph,
);

export default router;
2 changes: 1 addition & 1 deletion src/routes/v1/memberActivity.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ router.post(
router.post(
'/:platformId/members-interactions-network-graph',
auth('admin', 'view'),
validate(memberActivityValidation.memberInteractionsGraph),
validate(memberActivityValidation.membersInteractionsNetworkGraph),
memberActivityController.membersInteractionsNetworkGraph,
);
router.get(
Expand Down
2 changes: 2 additions & 0 deletions src/services/discourse/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import heatmapService from './heatmap.service';
import categoryService from './category.service';
import coreService from './core.service';
import memberActivityService from './memberActivity.service';
export default {
heatmapService,
coreService,
categoryService,
memberActivityService,
};
146 changes: 146 additions & 0 deletions src/services/discourse/memberActivity.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import NodeStats from '../../utils/enums/nodeStats.enum';
import * as Neo4j from '../../neo4j';
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';

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

type networkGraphUserInformationType = {
username: string;
avatar: string | null | undefined;
joinedAt: Date | null;
roles: [];
ngu: string;
};
function getUserInformationForNetworkGraph(user: any): networkGraphUserInformationType {
return {
username: user.options.username,
avatar: user.options.avatar,
joinedAt: user.joined_at,
roles: [],
ngu: user.options.name,
};
}

type memberInteractionType = { id: string; radius: number; stats: NodeStats } & networkGraphUserInformationType;
type memberInteractionsGraphResponseType = { width: number; from: memberInteractionType; to: memberInteractionType }[];
async function getMembersInteractionsNetworkGraph(
platformId: string,
platformName: SupportedNeo4jPlatforms,
): Promise<memberInteractionsGraphResponseType> {
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 = `
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 = `
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 = `
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;
}

export default {
getMembersInteractionsNetworkGraph,
};
2 changes: 1 addition & 1 deletion src/services/heatmap.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { IHeatmapChartRequestBody } from '../interfaces/Request.interface';
import { date, math } from '../utils';
import parentLogger from '../config/logger';

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

/**
* get heatmap chart
Expand Down
2 changes: 1 addition & 1 deletion src/services/memberActivity.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Snowflake } from 'discord.js';
import parentLogger from '../config/logger';
import { NEO4J_PLATFORM_INFO } from '../constants/neo4j.constant';
import { SupportedNeo4jPlatforms } from '../types/neo4j.type';
const logger = parentLogger.child({ module: 'MemberActivityService' });
const logger = parentLogger.child({ module: 'DiscordMemberActivityService' });

/**
* active members composition line graph
Expand Down
9 changes: 9 additions & 0 deletions src/validations/discourse.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ const lineGraph = {
}),
};

const membersInteractionsNetworkGraph = {
params: Joi.object()
.required()
.keys({
platformId: Joi.string().custom(objectId),
}),
};

export default {
heatmapChart,
lineGraph,
membersInteractionsNetworkGraph,
};
4 changes: 2 additions & 2 deletions src/validations/memberActivity.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const inactiveMembersLineGraph = {
}),
};

const memberInteractionsGraph = {
const membersInteractionsNetworkGraph = {
params: Joi.object()
.required()
.keys({
Expand Down Expand Up @@ -178,7 +178,7 @@ export default {
activeMembersOnboardingLineGraph,
disengagedMembersCompositionLineGraph,
inactiveMembersLineGraph,
memberInteractionsGraph,
membersInteractionsNetworkGraph,
decentralisationScore,
fragmentationScore,
activeMembersCompositionTable,
Expand Down

0 comments on commit 4925ab9

Please sign in to comment.