Skip to content

Commit 9d8dc64

Browse files
authored
Merge pull request #1309 from bksh05/feature/add-data-in-groups-api
Added member count and creator details in the GET discord-actions/groups API.
2 parents 528a586 + fbbfb54 commit 9d8dc64

File tree

10 files changed

+258
-5
lines changed

10 files changed

+258
-5
lines changed

controllers/discordactions.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const admin = require("firebase-admin");
33
const config = require("config");
44
const jwt = require("jsonwebtoken");
55
const discordRolesModel = require("../models/discordactions");
6+
const { retrieveUsers } = require("../services/dataAccessLayer");
67

78
/**
89
* Creates a role
@@ -66,9 +67,25 @@ const createGroupRole = async (req, res) => {
6667
const getAllGroupRoles = async (req, res) => {
6768
try {
6869
const { groups } = await discordRolesModel.getAllGroupRoles();
70+
const groupsWithMemberCount = await discordRolesModel.getNumberOfMemberForGroups(groups);
71+
const groupCreatorIds = groupsWithMemberCount.reduce((ids, group) => {
72+
ids.add(group.createdBy);
73+
return ids;
74+
}, new Set());
75+
const groupCreatorsDetails = await retrieveUsers({ userIds: Array.from(groupCreatorIds) });
76+
const groupsWithUserDetails = groupsWithMemberCount.map((group) => {
77+
const groupCreator = groupCreatorsDetails[group.createdBy];
78+
return {
79+
...group,
80+
firstName: groupCreator.first_name,
81+
lastName: groupCreator.last_name,
82+
image: groupCreator.picture?.url,
83+
};
84+
});
85+
6986
return res.json({
7087
message: "Roles fetched successfully!",
71-
groups,
88+
groups: groupsWithUserDetails,
7289
});
7390
} catch (err) {
7491
logger.error(`Error while getting roles: ${err}`);

models/discordactions.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,41 @@ const updateDiscordImageForVerification = async (userDiscordId) => {
114114
}
115115
};
116116

117+
const getNumberOfMemberForGroups = async (groups = []) => {
118+
try {
119+
if (!groups.length) {
120+
return [];
121+
}
122+
const roleIds = groups.map((group) => group.roleid);
123+
124+
const snapshots = await memberRoleModel.where("roleid", "in", roleIds).get();
125+
const roleCount = {};
126+
127+
snapshots.forEach((doc) => {
128+
const roleToMemberMapping = doc.data();
129+
130+
if (roleCount[roleToMemberMapping.roleid]) {
131+
roleCount[roleToMemberMapping.roleid] += 1;
132+
} else {
133+
roleCount[roleToMemberMapping.roleid] = 1;
134+
}
135+
});
136+
137+
return groups.map((group) => ({
138+
...group,
139+
memberCount: roleCount[group.roleid] || 0,
140+
}));
141+
} catch (err) {
142+
logger.error("Error while counting members for each group", err);
143+
throw err;
144+
}
145+
};
146+
117147
module.exports = {
118148
createNewRole,
119149
getAllGroupRoles,
120150
addGroupRoleToMember,
121151
isGroupRoleExists,
122152
updateDiscordImageForVerification,
153+
getNumberOfMemberForGroups,
123154
};

models/users.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,31 @@ const fetchUsersWithToken = async () => {
584584
return users;
585585
} catch (err) {
586586
logger.error(`Error while fetching all users with tokens field: ${err}`);
587+
return [];
588+
}
589+
};
590+
/**
591+
*
592+
* @param {[string]} userIds Array id's of user
593+
* @returns Object containing the details of the users whose userId was provided.
594+
*/
595+
const fetchUserByIds = async (userIds = []) => {
596+
if (userIds.length === 0) {
597+
return {};
598+
}
599+
try {
600+
const users = {};
601+
const usersRefs = userIds.map((docId) => userModel.doc(docId));
602+
const documents = await firestore.getAll(...usersRefs);
603+
documents.forEach((snapshot) => {
604+
if (snapshot.exists) {
605+
users[snapshot.id] = snapshot.data();
606+
}
607+
});
608+
609+
return users;
610+
} catch (err) {
611+
logger.error("Error retrieving user data", err);
587612
throw err;
588613
}
589614
};
@@ -666,4 +691,5 @@ module.exports = {
666691
fetchUsersWithToken,
667692
removeGitHubToken,
668693
getUsersByRole,
694+
fetchUserByIds,
669695
};

services/dataAccessLayer.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ const userQuery = require("../models/users");
22
const members = require("../models/members");
33
const { USER_SENSITIVE_DATA } = require("../constants/users");
44

5-
const retrieveUsers = async ({ id = null, username = null, usernames = null, query = null, userdata }) => {
5+
const retrieveUsers = async ({
6+
id = null,
7+
username = null,
8+
usernames = null,
9+
query = null,
10+
userdata,
11+
userIds = [],
12+
}) => {
613
if (id || username) {
714
let result;
815
if (id != null) {
@@ -18,6 +25,14 @@ const retrieveUsers = async ({ id = null, username = null, usernames = null, que
1825
removeSensitiveInfo(element);
1926
});
2027
return users;
28+
} else if (userIds.length > 0) {
29+
const userDetails = await userQuery.fetchUserByIds(userIds);
30+
31+
Object.keys(userDetails).forEach((userId) => {
32+
removeSensitiveInfo(userDetails[userId]);
33+
});
34+
35+
return userDetails;
2136
} else if (query) {
2237
const { allUsers, nextId, prevId } = await userQuery.fetchPaginatedUsers(query);
2338
allUsers.forEach((element) => {

test/fixtures/discordactions/discordactions.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const groupData = [
2-
{ id: "1", name: "Group 1" },
3-
{ id: "2", name: "Group 2" },
4-
{ id: "3", name: "Group 3" },
2+
{ rolename: "Group 1", roleid: 1 },
3+
{ rolename: "Group 2", roleid: 2 },
4+
{ rolename: "Group 3", roleid: 3 },
55
];
66

77
const roleData = {

test/integration/discord.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ const authService = require("../../services/authService");
88
const userData = require("../fixtures/user/user")();
99
const { requestRoleData } = require("../fixtures/discordactions/discordactions");
1010

11+
const firestore = require("../../utils/firestore");
12+
const discordRoleModel = firestore.collection("discord-roles");
13+
const userModel = firestore.collection("users");
14+
15+
const { addGroupRoleToMember } = require("../../models/discordactions");
16+
17+
const { groupData } = require("../fixtures/discordactions/discordactions");
18+
1119
const cookieName = config.get("userToken.cookieName");
1220

1321
let userId;
@@ -59,6 +67,29 @@ describe("test discord actions", function () {
5967
const user = { ...userData[4], discordId: "123456789" };
6068
userId = await addUser(user);
6169
jwt = authService.generateAuthToken({ userId });
70+
71+
let allIds = [];
72+
73+
const addUsersPromises = userData.map((user) => userModel.add({ ...user }));
74+
const responses = await Promise.all(addUsersPromises);
75+
allIds = responses.map((response) => response.id);
76+
77+
const addRolesPromises = [
78+
discordRoleModel.add({ roleid: groupData[0].roleid, rolename: groupData[0].rolename, createdBy: allIds[1] }),
79+
discordRoleModel.add({ roleid: groupData[1].roleid, rolename: groupData[1].rolename, createdBy: allIds[0] }),
80+
];
81+
await Promise.all(addRolesPromises);
82+
83+
const addGroupRolesPromises = [
84+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[0] }),
85+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[1] }),
86+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[1] }),
87+
addGroupRoleToMember({ roleid: groupData[1].roleid, userid: allIds[0] }),
88+
];
89+
await Promise.all(addGroupRolesPromises);
90+
});
91+
afterEach(async function () {
92+
await cleanDb();
6293
});
6394

6495
it("returns 200 for active users get method", function (done) {

test/integration/discordactions.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const cookieName = config.get("userToken.cookieName");
1616
const firestore = require("../../utils/firestore");
1717
const { userPhotoVerificationData } = require("../fixtures/user/photo-verification");
1818
const photoVerificationModel = firestore.collection("photo-verification");
19+
const discordRoleModel = firestore.collection("discord-roles");
20+
const userModel = firestore.collection("users");
21+
22+
const { groupData } = require("../fixtures/discordactions/discordactions");
23+
const { addGroupRoleToMember } = require("../../models/discordactions");
1924
chai.use(chaiHttp);
2025

2126
describe("Discord actions", function () {
@@ -85,4 +90,54 @@ describe("Discord actions", function () {
8590
});
8691
});
8792
});
93+
94+
describe("GET /discord-actions/groups", function () {
95+
before(async function () {
96+
let allIds = [];
97+
98+
const addUsersPromises = userData.map((user) => userModel.add({ ...user }));
99+
const responses = await Promise.all(addUsersPromises);
100+
allIds = responses.map((response) => response.id);
101+
102+
const addRolesPromises = [
103+
discordRoleModel.add({ roleid: groupData[0].roleid, rolename: groupData[0].rolename, createdBy: allIds[1] }),
104+
discordRoleModel.add({ roleid: groupData[1].roleid, rolename: groupData[1].rolename, createdBy: allIds[0] }),
105+
];
106+
await Promise.all(addRolesPromises);
107+
108+
const addGroupRolesPromises = [
109+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[0] }),
110+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[1] }),
111+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: allIds[1] }),
112+
addGroupRoleToMember({ roleid: groupData[1].roleid, userid: allIds[0] }),
113+
];
114+
await Promise.all(addGroupRolesPromises);
115+
});
116+
117+
after(async function () {
118+
await cleanDb();
119+
});
120+
121+
it("should successfully return all groups detail", function (done) {
122+
chai
123+
.request(app)
124+
.get(`/discord-actions/groups`)
125+
.set("cookie", `${cookieName}=${superUserAuthToken}`)
126+
.end((err, res) => {
127+
if (err) {
128+
return done(err);
129+
}
130+
131+
expect(res).to.have.status(200);
132+
expect(res.body).to.be.an("object");
133+
// Verify presence of specific properties in each group
134+
const expectedProps = ["roleid", "rolename", "memberCount", "firstName", "lastName", "image"];
135+
res.body.groups.forEach((group) => {
136+
expect(group).to.include.all.keys(expectedProps);
137+
});
138+
expect(res.body.message).to.equal("Roles fetched successfully!");
139+
return done();
140+
});
141+
});
142+
});
88143
});

test/unit/models/discordactions.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
isGroupRoleExists,
1313
addGroupRoleToMember,
1414
updateDiscordImageForVerification,
15+
getNumberOfMemberForGroups,
1516
} = require("../../../models/discordactions");
1617
const { groupData, roleData, existingRole } = require("../../fixtures/discordactions/discordactions");
1718
const cleanDb = require("../../utils/cleanDb");
@@ -243,4 +244,40 @@ describe("discordactions", function () {
243244
}
244245
});
245246
});
247+
248+
describe("getNumberOfMemberForGroups", function () {
249+
before(async function () {
250+
await Promise.all([
251+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: 1 }),
252+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: 2 }),
253+
addGroupRoleToMember({ roleid: groupData[0].roleid, userid: 3 }),
254+
addGroupRoleToMember({ roleid: groupData[1].roleid, userid: 1 }),
255+
]);
256+
});
257+
258+
after(async function () {
259+
await cleanDb();
260+
});
261+
262+
it("should return an empty array if the parameter is an empty array", async function () {
263+
const result = await getNumberOfMemberForGroups([]);
264+
expect(result).to.be.an("array");
265+
expect(result.length).to.equal(0);
266+
});
267+
268+
it("should return an empty array if the parameter no parameter is passed", async function () {
269+
const result = await getNumberOfMemberForGroups();
270+
expect(result).to.be.an("array");
271+
expect(result.length).to.equal(0);
272+
});
273+
274+
it("should return group details with memberCount details ", async function () {
275+
const result = await getNumberOfMemberForGroups(groupData);
276+
expect(result).to.deep.equal([
277+
{ rolename: groupData[0].rolename, roleid: 1, memberCount: 3 },
278+
{ rolename: groupData[1].rolename, roleid: 2, memberCount: 1 },
279+
{ rolename: groupData[2].rolename, roleid: 3, memberCount: 0 },
280+
]);
281+
});
282+
});
246283
});

test/unit/models/users.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,32 @@ describe("users", function () {
290290
});
291291
});
292292
});
293+
describe("fetch users by id", function () {
294+
let allIds = [];
295+
before(async function () {
296+
const addUsersPromises = [];
297+
userDataArray.forEach((user, index) => {
298+
addUsersPromises.push(userModel.add({ ...user }));
299+
});
300+
const responses = await Promise.all(addUsersPromises);
301+
allIds = responses.map((response) => response.id);
302+
});
303+
304+
after(async function () {
305+
await cleanDb();
306+
});
307+
308+
it("should fetch the details of users whose ids are present in the array", async function () {
309+
const randomIds = allIds.sort(() => 0.5 - Math.random()).slice(0, 3); // Select random ids from allIds
310+
const result = await users.fetchUserByIds(randomIds);
311+
const fetchedUserIds = Object.keys(result);
312+
expect(fetchedUserIds).to.deep.equal(randomIds);
313+
});
314+
315+
it("should return empty object if no ids are passed", async function () {
316+
const result = await users.fetchUserByIds();
317+
const fetchedUserIds = Object.keys(result);
318+
expect(fetchedUserIds).to.deep.equal([]);
319+
});
320+
});
293321
});

test/unit/services/dataAccessLayer.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@ describe("Data Access Layer", function () {
121121
});
122122
});
123123
});
124+
125+
it("should fetch multiple users details based on ids and remove sensitive data", async function () {
126+
const fetchUserStub = sinon.stub(userQuery, "fetchUserByIds");
127+
fetchUserStub.returns(Promise.resolve({ [userData[12].id]: userData[12] }));
128+
const result = await retrieveUsers({ userIds: [userData[12].id] });
129+
removeSensitiveInfo(userData[12]);
130+
Object.keys(result).forEach((id) => {
131+
expect(result[id]).to.deep.equal(userData[12]);
132+
USER_SENSITIVE_DATA.forEach((key) => {
133+
expect(result[id]).to.not.have.property(key);
134+
});
135+
});
136+
});
124137
});
125138

126139
describe("retrieveFilteredUsers", function () {

0 commit comments

Comments
 (0)