Skip to content

Commit ebda0d2

Browse files
authored
Merge branch 'develop' into fix/firebase-query-limit
2 parents 3fee6fc + 74f8324 commit ebda0d2

File tree

7 files changed

+299
-19
lines changed

7 files changed

+299
-19
lines changed

constants/logs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export const logType = {
1010
TASKS_MISSED_UPDATES_ERRORS: "TASKS_MISSED_UPDATES_ERRORS",
1111
DISCORD_INVITES: "DISCORD_INVITES",
1212
EXTERNAL_SERVICE: "EXTERNAL_SERVICE",
13+
ADD_UNVERIFIED_ROLE: "ADD_UNVERIFIED_ROLE",
14+
REMOVE_ROLE_FROM_USER_SUCCESS: "REMOVE_ROLE_FROM_USER_SUCCESS",
15+
REMOVE_ROLE_FROM_USER_FAILED: "REMOVE_ROLE_FROM_USER_FAILED",
1316
EXTENSION_REQUESTS: "extensionRequests",
1417
TASK: "task",
1518
TASK_REQUESTS: "taskRequests",

controllers/external-accounts.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const { getDiscordMembers } = require("../services/discordService");
44
const { addOrUpdate, getUsersByRole, updateUsersInBatch } = require("../models/users");
55
const { retrieveDiscordUsers, fetchUsersForKeyValues } = require("../services/dataAccessLayer");
66
const { EXTERNAL_ACCOUNTS_POST_ACTIONS } = require("../constants/external-accounts");
7-
const discordServices = require("../services/discordService");
7+
const removeDiscordRoleUtils = require("../utils/removeDiscordRoleFromUser");
88
const config = require("config");
99
const logger = require("../utils/logger");
1010
const { markUnDoneTasksOfArchivedUsersBacklog } = require("../models/tasks");
@@ -71,14 +71,16 @@ const linkExternalAccount = async (req, res) => {
7171
userId
7272
);
7373

74-
try {
75-
const unverifiedRoleId = config.get("discordUnverifiedRoleId");
76-
await discordServices.removeRoleFromUser(unverifiedRoleId, attributes.discordId, req.userData);
77-
} catch (error) {
78-
logger.error(`Error getting external account data: ${error}`);
79-
return res.boom.internal("Role Deletion failed. Please contact admin.", {
80-
message: "Role Deletion failed. Please contact admin.",
81-
});
74+
const unverifiedRoleId = config.get("discordUnverifiedRoleId");
75+
const unverifiedRoleRemovalResponse = await removeDiscordRoleUtils.removeDiscordRoleFromUser(
76+
req.userData,
77+
attributes.discordId,
78+
unverifiedRoleId
79+
);
80+
81+
if (!unverifiedRoleRemovalResponse.success) {
82+
const message = `User details updated but ${unverifiedRoleRemovalResponse.message}. Please contact admin`;
83+
return res.boom.internal(message, { message });
8284
}
8385

8486
return res.status(204).json({ message: "Your discord profile has been linked successfully" });

controllers/users.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
const chaincodeQuery = require("../models/chaincodes");
22
const userQuery = require("../models/users");
33
const profileDiffsQuery = require("../models/profileDiffs");
4+
const firestore = require("../utils/firestore");
5+
const memberRoleModel = firestore.collection("member-group-roles");
6+
const logsModel = firestore.collection("logs");
7+
const admin = require("firebase-admin");
48
const logsQuery = require("../models/logs");
59
const imageService = require("../services/imageService");
610
const { profileDiffStatus } = require("../constants/profileDiff");
@@ -620,7 +624,7 @@ const markUnverified = async (req, res) => {
620624
const unverifiedRoleId = config.get("discordUnverifiedRoleId");
621625
const usersToApplyUnverifiedRole = [];
622626
const addRolePromises = [];
623-
const discordDeveloperRoleId = config.get("discordDeveloperRoleId");
627+
const batchPromises = [];
624628

625629
allRdsLoggedInUsers.forEach((user) => {
626630
rdsUserMap[user.discordId] = true;
@@ -636,11 +640,40 @@ const markUnverified = async (req, res) => {
636640
}
637641
});
638642

643+
const batchSize = 500;
644+
const batches = Array.from({ length: Math.ceil(usersToApplyUnverifiedRole.length / batchSize) }, (_, index) =>
645+
usersToApplyUnverifiedRole.slice(index * batchSize, index * batchSize + batchSize)
646+
);
647+
648+
batches.forEach((batch) => {
649+
const firestoreBatch = firestore.batch();
650+
651+
batch.forEach((id) => {
652+
const memberRoleRef = memberRoleModel.doc(id);
653+
const logRef = logsModel.doc();
654+
655+
firestoreBatch.set(memberRoleRef, {
656+
roleid: unverifiedRoleId,
657+
userid: id,
658+
date: admin.firestore.Timestamp.fromDate(new Date()),
659+
});
660+
661+
firestoreBatch.set(logRef, {
662+
type: logType.ADD_UNVERIFIED_ROLE,
663+
meta: { roleid: unverifiedRoleId, userid: id },
664+
body: { message: "Unverified role added successfully" },
665+
timestamp: admin.firestore.Timestamp.fromDate(new Date()),
666+
});
667+
});
668+
669+
batchPromises.push(firestoreBatch.commit());
670+
});
671+
639672
usersToApplyUnverifiedRole.forEach((id) => {
640673
addRolePromises.push(addRoleToUser(id, unverifiedRoleId));
641674
});
642675

643-
await Promise.all(addRolePromises);
676+
await Promise.all([...addRolePromises, ...batchPromises]);
644677
return res.json({ message: "ROLES APPLIED SUCCESSFULLY" });
645678
} catch (err) {
646679
logger.error(err);

models/discordactions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const updateGroupRole = async (roleData, docId) => {
158158
*
159159
* @param options { Object }: Data of the new role
160160
* @param options.rolename String : name of the role
161-
* @param options.roleId String : id of the role
161+
* @param options.roleid String : id of the role
162162
* @returns {Promise<discordRoleModel|Object>}
163163
*/
164164

test/integration/external-accounts.test.js

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ const externalAccountsModel = require("../../models/external-accounts");
1212
const { usersFromRds, getDiscordMembers } = require("../fixtures/discordResponse/discord-response");
1313
const Sinon = require("sinon");
1414
const { INTERNAL_SERVER_ERROR } = require("../../constants/errorMessages");
15+
const removeDiscordRoleUtils = require("../../utils/removeDiscordRoleFromUser");
1516
const firestore = require("../../utils/firestore");
1617
const userData = require("../fixtures/user/user")();
1718
const userModel = firestore.collection("users");
1819
const tasksModel = firestore.collection("tasks");
19-
const discordServices = require("../../services/discordService");
2020
const { EXTERNAL_ACCOUNTS_POST_ACTIONS } = require("../../constants/external-accounts");
2121
chai.use(chaiHttp);
2222
const cookieName = config.get("userToken.cookieName");
@@ -538,7 +538,10 @@ describe("External Accounts", function () {
538538
expect(getUserResponseBeforeUpdate.body).to.not.have.property("discordId");
539539
expect(getUserResponseBeforeUpdate.body).to.not.have.property("discordJoinedAt");
540540

541-
const removeRoleFromUserStub = Sinon.stub(discordServices, "removeRoleFromUser").resolves();
541+
const removeDiscordRoleStub = Sinon.stub(removeDiscordRoleUtils, "removeDiscordRoleFromUser").resolves({
542+
success: true,
543+
message: "Role deleted successfully",
544+
});
542545

543546
const response = await chai
544547
.request(app)
@@ -557,26 +560,85 @@ describe("External Accounts", function () {
557560
expect(updatedUserDetails.body).to.have.property("discordId");
558561
expect(updatedUserDetails.body).to.have.property("discordJoinedAt");
559562

560-
removeRoleFromUserStub.restore();
563+
removeDiscordRoleStub.restore();
564+
});
565+
566+
it("Should return 500 when removeDiscordRole fails because role doesn't exist", async function () {
567+
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);
568+
569+
const removeDiscordRoleStub = Sinon.stub(removeDiscordRoleUtils, "removeDiscordRoleFromUser").resolves({
570+
success: false,
571+
message: "Role doesn't exist",
572+
});
573+
574+
const response = await chai
575+
.request(app)
576+
.patch(`/external-accounts/link/${externalAccountData[2].token}`)
577+
.query({ action: EXTERNAL_ACCOUNTS_POST_ACTIONS.DISCORD_USERS_SYNC })
578+
.set("Cookie", `${cookieName}=${newUserJWT}`);
579+
580+
const unverifiedRoleRemovalResponse = await removeDiscordRoleStub();
581+
582+
expect(response).to.have.status(500);
583+
expect(response.body).to.be.an("object");
584+
expect(response.body).to.have.property("message");
585+
expect(response.body.message).to.equal(
586+
`User details updated but ${unverifiedRoleRemovalResponse.message}. Please contact admin`
587+
);
588+
589+
removeDiscordRoleStub.restore();
590+
});
591+
592+
it("Should return 500 when removeDiscordRole fails because role deletion from discord failed", async function () {
593+
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);
594+
595+
const removeDiscordRoleStub = Sinon.stub(removeDiscordRoleUtils, "removeDiscordRoleFromUser").resolves({
596+
success: false,
597+
message: "Role deletion from discord failed",
598+
});
599+
600+
const response = await chai
601+
.request(app)
602+
.patch(`/external-accounts/link/${externalAccountData[2].token}`)
603+
.query({ action: EXTERNAL_ACCOUNTS_POST_ACTIONS.DISCORD_USERS_SYNC })
604+
.set("Cookie", `${cookieName}=${newUserJWT}`);
605+
606+
const unverifiedRoleRemovalResponse = await removeDiscordRoleStub();
607+
608+
expect(response).to.have.status(500);
609+
expect(response.body).to.be.an("object");
610+
expect(response.body).to.have.property("message");
611+
expect(response.body.message).to.equal(
612+
`User details updated but ${unverifiedRoleRemovalResponse.message}. Please contact admin`
613+
);
614+
615+
removeDiscordRoleStub.restore();
561616
});
562617

563-
it("Should return 500 when unverified role deletion failed", async function () {
618+
it("Should return 500 when removeDiscordRole fails because role deletion from database failed", async function () {
564619
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);
565620

566-
const removeRoleFromUserStub = Sinon.stub(discordServices, "removeRoleFromUser").rejects();
621+
const removeDiscordRoleStub = Sinon.stub(removeDiscordRoleUtils, "removeDiscordRoleFromUser").resolves({
622+
success: false,
623+
message: "Role deletion from database failed",
624+
});
567625

568626
const response = await chai
569627
.request(app)
570628
.patch(`/external-accounts/link/${externalAccountData[2].token}`)
571629
.query({ action: EXTERNAL_ACCOUNTS_POST_ACTIONS.DISCORD_USERS_SYNC })
572630
.set("Cookie", `${cookieName}=${newUserJWT}`);
573631

632+
const unverifiedRoleRemovalResponse = await removeDiscordRoleStub();
633+
574634
expect(response).to.have.status(500);
575635
expect(response.body).to.be.an("object");
576636
expect(response.body).to.have.property("message");
577-
expect(response.body.message).to.equal(`Role Deletion failed. Please contact admin.`);
637+
expect(response.body.message).to.equal(
638+
`User details updated but ${unverifiedRoleRemovalResponse.message}. Please contact admin`
639+
);
578640

579-
removeRoleFromUserStub.restore();
641+
removeDiscordRoleStub.restore();
580642
});
581643
});
582644
});
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import chai from "chai";
2+
import Sinon from "sinon";
3+
import { logType } from "../../../constants/logs";
4+
import discordActions from "../../../models/discordactions";
5+
import { addLog } from "../../../models/logs";
6+
import firestore from "../../../utils/firestore";
7+
import { removeDiscordRoleFromUser } from "../../../utils/removeDiscordRoleFromUser";
8+
import { groupData, memberGroupData } from "../../fixtures/discordactions/discordactions";
9+
import addUser from "../../utils/addUser";
10+
import cleanDb from "../../utils/cleanDb";
11+
const { expect } = chai;
12+
const discordRolesModel = firestore.collection("discord-roles");
13+
const memberRoleModel = firestore.collection("member-group-roles");
14+
const logsModel = firestore.collection("logs");
15+
const userData = require("../../fixtures/user/user")();
16+
17+
describe("removeDiscordRoleFromUser", function () {
18+
let userId;
19+
let discordId;
20+
let roleid;
21+
let fetchStub;
22+
23+
beforeEach(async function () {
24+
userData[0].roles = { archived: false, in_discord: true };
25+
userId = await addUser(userData[0]);
26+
discordId = userData[0].discordId;
27+
userData[0] = { ...userData[0], id: userId };
28+
29+
const addRolePromises = memberGroupData.map(async (data) => {
30+
await memberRoleModel.add(data);
31+
});
32+
const discordRolesModelPromise = [discordRolesModel.add(groupData[0]), discordRolesModel.add(groupData[1])];
33+
await Promise.all(discordRolesModelPromise);
34+
roleid = groupData[0].roleid;
35+
await memberRoleModel.add({ roleid, userid: discordId });
36+
await Promise.all(addRolePromises);
37+
38+
fetchStub = Sinon.stub(global, "fetch");
39+
});
40+
41+
afterEach(async function () {
42+
await cleanDb();
43+
fetchStub.restore();
44+
});
45+
46+
it("should remove discord role successfully", async function () {
47+
await addLog(
48+
logType.REMOVE_ROLE_FROM_USER_SUCCESS,
49+
{ roleId: roleid, userid: discordId },
50+
{ message: "Role removed successfully from user", userData: userData[0] }
51+
);
52+
53+
fetchStub.returns(
54+
Promise.resolve({ json: () => Promise.resolve({ success: true, message: "Role deleted successfully" }) })
55+
);
56+
57+
const isDiscordRoleRemoved = await removeDiscordRoleFromUser(userData[0], discordId, roleid);
58+
const successLog = await logsModel
59+
.where("type", "==", logType.REMOVE_ROLE_FROM_USER_SUCCESS)
60+
.where("meta.roleId", "==", roleid)
61+
.where("meta.userid", "==", discordId)
62+
.limit(1)
63+
.get();
64+
65+
expect(isDiscordRoleRemoved.success).to.be.equal(true);
66+
expect(isDiscordRoleRemoved.message).to.be.equal("Role deleted successfully");
67+
expect(successLog.docs[0].data().body.message).to.be.equal("Role removed successfully from user");
68+
});
69+
70+
it("should throw an error if role doesn't exist in database when attempting to remove", async function () {
71+
roleid = "randomRoleId";
72+
73+
const isDiscordRoleRemoved = await removeDiscordRoleFromUser(userData[0], discordId, roleid);
74+
const failedLog = await logsModel
75+
.where("type", "==", logType.REMOVE_ROLE_FROM_USER_FAILED)
76+
.where("meta.roleId", "==", roleid)
77+
.limit(1)
78+
.get();
79+
80+
expect(isDiscordRoleRemoved.success).to.be.equal(false);
81+
expect(isDiscordRoleRemoved.message).to.be.equal("Role doesn't exist");
82+
expect(failedLog.docs[0].data().body.message).to.be.equal("Role doesn't exist");
83+
});
84+
85+
it("should throw an error if role deletion from discord failed", async function () {
86+
fetchStub.rejects(new Error("Role deletion from discord failed"));
87+
88+
const isDiscordRoleRemoved = await removeDiscordRoleFromUser(userData[0], discordId, roleid);
89+
const failedLog = await logsModel
90+
.where("type", "==", logType.REMOVE_ROLE_FROM_USER_FAILED)
91+
.where("meta.roleId", "==", roleid)
92+
.where("meta.userid", "==", discordId)
93+
.limit(1)
94+
.get();
95+
96+
expect(isDiscordRoleRemoved.success).to.be.equal(false);
97+
expect(isDiscordRoleRemoved.message).to.be.equal("Role deletion from discord failed");
98+
expect(failedLog.docs[0].data().body.message).to.be.equal("Role deletion from discord failed");
99+
});
100+
101+
it("should throw an error if role deleted from discord but not from database", async function () {
102+
fetchStub.returns(Promise.resolve({ json: () => Promise.resolve({ success: true }) }));
103+
104+
const removeMemberGroupStub = Sinon.stub(discordActions, "removeMemberGroup").resolves({
105+
roleId: roleid,
106+
wasSuccess: false,
107+
});
108+
109+
const isDiscordRoleRemoved = await removeDiscordRoleFromUser(userData[0], discordId, roleid);
110+
const failedLog = await logsModel
111+
.where("type", "==", logType.REMOVE_ROLE_FROM_USER_FAILED)
112+
.where("meta.roleId", "==", roleid)
113+
.where("meta.userid", "==", discordId)
114+
.limit(1)
115+
.get();
116+
117+
expect(isDiscordRoleRemoved.success).to.be.equal(false);
118+
expect(isDiscordRoleRemoved.message).to.be.equal("Role deletion from database failed");
119+
expect(failedLog.docs[0].data().body.message).to.be.equal("Role deletion from database failed");
120+
121+
removeMemberGroupStub.restore();
122+
});
123+
});

0 commit comments

Comments
 (0)