Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 0 additions & 73 deletions .github/workflows/codeql.yml

This file was deleted.

47 changes: 33 additions & 14 deletions models/discordactions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { generateDiscordProfileImageUrl } = require("../utils/discord-actions");
const discordService = require("../services/discordService");
const { addRoleToUser, removeRoleFromUser } = discordService;
const firestore = require("../utils/firestore");
const discordRoleModel = firestore.collection("discord-roles");
const memberRoleModel = firestore.collection("member-group-roles");
Expand All @@ -14,13 +15,13 @@ const { ONE_DAY_IN_MS, SIMULTANEOUS_WORKER_CALLS } = require("../constants/users
const userModel = firestore.collection("users");
const photoVerificationModel = firestore.collection("photo-verification");
const dataAccess = require("../services/dataAccessLayer");
const { getDiscordMembers, addRoleToUser, removeRoleFromUser } = require("../services/discordService");
const discordDeveloperRoleId = config.get("discordDeveloperRoleId");
const discordMavenRoleId = config.get("discordMavenRoleId");
const discordMissedUpdatesRoleId = config.get("discordMissedUpdatesRoleId");

const userStatusModel = firestore.collection("usersStatus");
const usersUtils = require("../utils/users");
const { generateDiscordProfileImageUrl } = require("../utils/discord-actions");
const { getUsersBasedOnFilter, fetchUser } = require("./users");
const {
convertDaysToMilliseconds,
Expand All @@ -30,7 +31,7 @@ const {
const { chunks } = require("../utils/array");
const tasksModel = firestore.collection("tasks");
const { FIRESTORE_IN_CLAUSE_SIZE } = require("../constants/users");
const discordService = require("../services/discordService");

const { buildTasksQueryForMissedUpdates } = require("../utils/tasks");
const { buildProgressQueryForMissedUpdates } = require("../utils/progresses");
const { getRequestByKeyValues } = require("./requests");
Expand Down Expand Up @@ -299,7 +300,8 @@ const updateDiscordImageForVerification = async (userDiscordId) => {
* @param {string} discordId - The Discord ID of the user.
* @param {Array<object>} groups - Array of group objects to process.
* @returns {Promise<Array<object>>} - An array of group objects with enriched information.
*/
**/

const enrichGroupDataWithMembershipInfo = async (discordId, groups = []) => {
try {
if (!groups.length) {
Expand All @@ -313,28 +315,44 @@ const enrichGroupDataWithMembershipInfo = async (discordId, groups = []) => {
return ids;
}, new Set());

const groupCreatorsDetails = await retrieveUsers({ userIds: Array.from(groupCreatorIds) });
const groupCreatorsDetails = await retrieveUsers({
userIds: Array.from(groupCreatorIds),
});

const roleIds = groups.map((group) => group.roleid);
const groupsToUserMappings = await fetchGroupToUserMapping(roleIds);

// Firestore-based mapping (used for isMember)
const roleIdToCountMap = {};
groupsToUserMappings.forEach((mapping) => {
roleIdToCountMap[mapping.roleid] = (roleIdToCountMap[mapping.roleid] ?? 0) + 1;
});

// Discord live role membership (used for memberCount)
const discordMembers = await discordService.getDiscordMembers();
const liveRoleIdToCountMap = new Map();

groupsToUserMappings.forEach((groupToUserMapping) => {
// Count how many times roleId comes up in the array.
// This says how many users we have for a given roleId
roleIdToCountMap[groupToUserMapping.roleid] = (roleIdToCountMap[groupToUserMapping.roleid] ?? 0) + 1;
discordMembers.forEach((member) => {
if (!member.roles) return;

member.roles.forEach((roleId) => {
liveRoleIdToCountMap.set(roleId, (liveRoleIdToCountMap.get(roleId) || 0) + 1);
});
});

const subscribedGroupIds = findSubscribedGroupIds(discordId, groupsToUserMappings);

return groups.map((group) => {
const groupCreator = groupCreatorsDetails.find((user) => user.id === group.createdBy);

return {
...group,
firstName: groupCreator?.first_name,
lastName: groupCreator?.last_name,
image: groupCreator?.picture?.url,
memberCount: roleIdToCountMap[group.roleid] || 0, // Number of users joined this group
isMember: subscribedGroupIds.has(group.roleid), // Is current loggedIn user is a member of this group
// Member count from Firestore mapping
memberCount: roleIdToCountMap[group.roleid] || 0,
isMember: subscribedGroupIds.has(group.roleid),
};
});
} catch (err) {
Expand All @@ -350,6 +368,7 @@ const enrichGroupDataWithMembershipInfo = async (discordId, groups = []) => {
*
* Breaking the roleIds array into chunks of 30 or less due to firebase limitation
*/

const fetchGroupToUserMapping = async (roleIds) => {
try {
const roleIdChunks = [];
Expand Down Expand Up @@ -392,7 +411,7 @@ const updateIdleUsersOnDiscord = async (dev) => {
groupIdleRoleId = groupIdleRole.role.roleid;
if (!groupIdleRole.roleExists) throw new Error("Idle Role does not exist");
const { allUserStatus } = await getAllUserStatus({ state: userState.IDLE });
const discordUsers = await getDiscordMembers();
const discordUsers = await discordService.getDiscordMembers();
const usersHavingIdleRole = [];
discordUsers?.forEach((discordUser) => {
const isDeveloper = discordUser.roles.includes(discordDeveloperRoleId);
Expand Down Expand Up @@ -617,7 +636,7 @@ const updateIdle7dUsersOnDiscord = async (dev) => {
if (!groupIdle7dRole.roleExists) throw new Error("Idle Role does not exist");

const { allUserStatus } = await getAllUserStatus({ state: userState.IDLE });
const discordUsers = await getDiscordMembers();
const discordUsers = await discordService.getDiscordMembers();
const usersHavingIdle7dRole = [];

discordUsers?.forEach((discordUser) => {
Expand Down Expand Up @@ -807,7 +826,7 @@ const updateUsersWith31DaysPlusOnboarding = async () => {
allOnboardingUsers31DaysCompleted
);

const discordMembers = await getDiscordMembers();
const discordMembers = await discordService.getDiscordMembers();
const groupOnboardingRole = await getGroupRole("group-onboarding-31d+");
const groupOnboardingRoleId = groupOnboardingRole.role.roleid;
if (!groupOnboardingRole.roleExists) throw new Error("Role does not exist");
Expand Down
72 changes: 53 additions & 19 deletions test/integration/discord.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const chai = require("chai");
const { expect } = chai;

const sinon = require("sinon");
const discordService = require("../../services/discordService");
const app = require("../../server");
const addUser = require("../utils/addUser");
const cleanDb = require("../utils/cleanDb");
Expand All @@ -12,6 +13,10 @@ const firestore = require("../../utils/firestore");
const discordRoleModel = firestore.collection("discord-roles");
const userModel = firestore.collection("users");

const userStatusModel = require("../../models/userStatus");
const { generateUserStatusData } = require("../fixtures/userStatus/userStatus");
const { userState } = require("../../constants/userStatus");

const { addGroupRoleToMember } = require("../../models/discordactions");

const { groupData } = require("../fixtures/discordactions/discordactions");
Expand Down Expand Up @@ -63,33 +68,62 @@ describe("test discord actions", function () {
});

describe("test discord actions for active users", function () {
let getDiscordMembersStub;
let userId;
let jwt;

beforeEach(async function () {
const user = { ...userData[4], discordId: "123456789" };
getDiscordMembersStub = sinon.stub(discordService, "getDiscordMembers").resolves([]);

const user = {
...userData[4],
discordId: "123456789",
roles: {
in_discord: true,
archived: false,
},
};
userId = await addUser(user);
await userStatusModel.updateUserStatus(userId, generateUserStatusData(userState.ACTIVE, Date.now(), Date.now()));

jwt = authService.generateAuthToken({ userId });

let allIds = [];
const addUsersPromises = userData.map((u) => userModel.add({ ...u }));

const addUsersPromises = userData.map((user) => userModel.add({ ...user }));
const responses = await Promise.all(addUsersPromises);
allIds = responses.map((response) => response.id);

const addRolesPromises = [
discordRoleModel.add({ roleid: groupData[0].roleid, rolename: groupData[0].rolename, createdBy: allIds[1] }),
discordRoleModel.add({ roleid: groupData[1].roleid, rolename: groupData[1].rolename, createdBy: allIds[0] }),
];
await Promise.all(addRolesPromises);

const addGroupRolesPromises = [
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[0] }),
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[1] }),
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[1] }),
addGroupRoleToMember({ roleid: groupData[1].roleid, userid: allIds[0] }),
];
await Promise.all(addGroupRolesPromises);
const allIds = responses.map((r) => r.id);

await Promise.all([
discordRoleModel.add({
roleid: groupData[0].roleid,
rolename: groupData[0].rolename,
createdBy: allIds[1],
}),
discordRoleModel.add({
roleid: groupData[1].roleid,
rolename: groupData[1].rolename,
createdBy: allIds[0],
}),
]);

await Promise.all([
addGroupRoleToMember({
roleid: groupData[0].roleid,
userid: allIds[0],
}),
addGroupRoleToMember({
roleid: groupData[0].roleid,
userid: allIds[1],
}),
addGroupRoleToMember({
roleid: groupData[1].roleid,
userid: allIds[0],
}),
]);
});

afterEach(async function () {
getDiscordMembersStub.restore();
await cleanDb();
});

Expand Down
8 changes: 8 additions & 0 deletions test/unit/models/discordactions.test.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the use of adding fetchStub here is unclear, test changes in this file don't seem to be using it, can you point me where is it being used?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetch stub isn’t used directly in this test. The request hits /discord-actions/groups, which internally calls getDiscordMembers().

The stub ensures that call doesn’t make a real Discord API request and keeps the integration test deterministic.

Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,16 @@ describe("discordactions", function () {
});

describe("enrichGroupDataWithMembershipInfo", function () {
let fetchStub;
let newGroupData;
let allIds = [];

before(async function () {
fetchStub = sinon.stub(global, "fetch").resolves({
status: 200,
json: async () => getDiscordMembers,
});

const addUsersPromises = userData.map((user) => userModel.add({ ...user }));
const responses = await Promise.all(addUsersPromises);
allIds = responses.map((response) => response.id);
Expand All @@ -460,6 +466,8 @@ describe("discordactions", function () {
});

after(async function () {
fetchStub.restore();
sinon.restore();
await cleanDb();
});

Expand Down