From 3a81f0240f7dfaadd3b8a1d772bad91707414f47 Mon Sep 17 00:00:00 2001 From: Akshat187 Date: Tue, 29 Apr 2025 19:36:11 +0530 Subject: [PATCH 1/4] added api path --- controllers/discordactions.js | 70 ++++++++++++ routes/discordactions.js | 3 + services/discordService.js | 34 ++++++ test/unit/models/discordactions.test.js | 136 ++++++++++++++++++++++++ 4 files changed, 243 insertions(+) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index f77758b00..c1e081bdb 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -63,6 +63,75 @@ const createGroupRole = async (req, res) => { return res.boom.badImplementation(INTERNAL_SERVER_ERROR); } }; +const editGroupRoles = async (req, res) => { + try { + const { groupId } = req.params; + const { roleName, description } = req.body; + + if (!roleName && !description) { + return res.boom.badRequest("At least one field (roleName or description) must be provided"); + } + + if (roleName && (roleName.length < 3 || roleName.length > 50)) { + return res.boom.badRequest("Role name must be between 3 and 50 characters"); + } + + if (description && description.length > 200) { + return res.boom.badRequest("Description must not exceed 200 characters"); + } + + const { roleExists, existingRoles } = await discordRolesModel.isGroupRoleExists({ + groupId, + }); + if (!roleExists) { + return res.boom.notFound("Group role not found"); + } + + const roleData = existingRoles.data(); + + const updateRequest = {}; + if (roleName) { + updateRequest.roleName = roleName; + } + if (description) { + updateRequest.description = description; + } + + let discordUpdateSuccess = true; + if (updateRequest.roleName) { + const discordUpdateResponse = await discordServices.updateDiscordGroupRole( + roleData.roleid, + updateRequest.roleName, + updateRequest.description + ); + if (!discordUpdateResponse.success) { + discordUpdateSuccess = false; + logger.error(`Failed to update role name in Discord for groupId: ${groupId}`); + } + } + + let firestoreUpdateSuccess = true; + if (updateRequest.description) { + try { + await discordRolesModel.updateGroupRole(groupId, { + description: updateRequest.description, + }); + } catch (error) { + firestoreUpdateSuccess = false; + logger.error(`Failed to update description in Firestore for groupId: ${groupId}`); + } + } + + if (!discordUpdateSuccess || !firestoreUpdateSuccess) { + return res.boom.badImplementation("Partial update failed. Check logs for details."); + } + + return res.boom.success("updated the roleName and description successfully"); + } catch (error) { + logger.error(`Error while editing group role: ${error}`); + return res.boom.badImplementation("Internal server error"); + } +}; /** * Controller function to handle the soft deletion of a group role. @@ -559,6 +628,7 @@ const getUserDiscordInvite = async (req, res) => { module.exports = { getGroupsRoleId, + editGroupRoles, createGroupRole, getPaginatedAllGroupRoles, addGroupRoleToMember, diff --git a/routes/discordactions.js b/routes/discordactions.js index 5475e0344..e979a8352 100644 --- a/routes/discordactions.js +++ b/routes/discordactions.js @@ -2,6 +2,7 @@ const express = require("express"); const authenticate = require("../middlewares/authenticate"); const { createGroupRole, + editGroupRoles, getGroupsRoleId, addGroupRoleToMember, deleteRole, @@ -34,6 +35,8 @@ const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndSe const router = express.Router(); router.post("/groups", authenticate, checkIsVerifiedDiscord, validateGroupRoleBody, createGroupRole); +router.patch("/groups/:groupId", authenticate, authorizeRoles([SUPERUSER]), editGroupRoles); +router.patch("/groups/:groupId", editGroupRoles); router.get("/groups", authenticate, checkIsVerifiedDiscord, validateLazyLoadingParams, getPaginatedAllGroupRoles); router.delete("/groups/:groupId", authenticate, checkIsVerifiedDiscord, authorizeRoles([SUPERUSER]), deleteGroupRole); router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember); diff --git a/services/discordService.js b/services/discordService.js index 1244fe9d3..cfe8918bc 100644 --- a/services/discordService.js +++ b/services/discordService.js @@ -70,6 +70,39 @@ const addRoleToUser = async (userid, roleid) => { return response; }; +const updateDiscordGroupRole = async (roleId, roleName, description) => { + const authToken = generateAuthTokenForCloudflare(); + try { + const response = await ( + await fetch(`${DISCORD_BASE_URL}/roles/${roleId})`, { + method: "PATCH", + body: JSON.stringify({ + roleName, + description, + }), + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${authToken}`, + }, + }) + ).json(); + + if (response.status === 204) { + return { + success: true, + message: "Role updated successfully", + }; + } + return { + success: false, + message: response.message || "Failed to update role in Discord", + }; + } catch (error) { + logger.error(`Error in updating group role in discord: ${error}`); + throw new Error(error); + } +}; + const removeRoleFromUser = async (roleId, discordId, userData) => { try { const headers = generateCloudFlareHeaders(userData); @@ -142,6 +175,7 @@ const deleteGroupRoleFromDiscord = async (roleId) => { module.exports = { getDiscordMembers, + updateDiscordGroupRole, getDiscordRoles, setInDiscordFalseScript, addRoleToUser, diff --git a/test/unit/models/discordactions.test.js b/test/unit/models/discordactions.test.js index 1a21c6f61..4840b90d5 100644 --- a/test/unit/models/discordactions.test.js +++ b/test/unit/models/discordactions.test.js @@ -55,6 +55,9 @@ const cleanDb = require("../../utils/cleanDb"); const { userPhotoVerificationData } = require("../../fixtures/user/photo-verification"); const userData = require("../../fixtures/user/user")(); const userStatusModel = require("../../../models/userStatus"); +const { editGroupRoles } = require("../../../controllers/discordactions"); +const discordRolesModel = require("../../../models/discordactions"); +const discordServices = require("../../../services/discordService"); const { getStatusData } = require("../../fixtures/userStatus/userStatus"); const usersStatusData = getStatusData(); const dataAccessLayer = require("../../../services/dataAccessLayer"); @@ -97,6 +100,139 @@ describe("discordactions", function () { }); }); + describe("editGroupRoles", function () { + let req, res; + + beforeEach(function () { + req = { + params: { + groupId: "group1", + }, + body: {}, + userData: { + id: "user1", + }, + }; + + res = { + boom: { + badRequest: sinon.stub(), + notFound: sinon.stub(), + badImplementation: sinon.stub(), + }, + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + + sinon.restore(); + }); + + it("should return 400 if neither roleName nor description is provided", async function () { + await editGroupRoles(req, res); + + expect(res.boom.badRequest.calledWith("At least one field (roleName or description) must be provided")).to.equal( + true + ); + }); + + it("should return 400 if roleName is less than 3 characters", async function () { + req.body.roleName = "ab"; + + await editGroupRoles(req, res); + + expect(res.boom.badRequest.calledWith("Role name must be between 3 and 50 characters")).to.equal(true); + }); + + it("should return 400 if description exceeds 200 characters", async function () { + req.body.description = "a".repeat(201); + + await editGroupRoles(req, res); + + expect(res.boom.badRequest.calledWith("Description must not exceed 200 characters")).to.equal(true); + }); + + it("should return 404 if group role does not exist", async function () { + req.body.roleName = "new-role-name"; + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: false, + }); + + await editGroupRoles(req, res); + + expect(res.boom.notFound.calledWith("Group role not found")).to.equal(true); + }); + + it("should update roleName and description successfully", async function () { + req.body = { + roleName: "new-role-name", + description: "Updated description", + }; + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { + data: () => ({ + roleid: "12345", + }), + }, + }); + sinon.stub(discordServices, "updateDiscordGroupRole").resolves({ + success: true, + }); + sinon.stub(discordRolesModel, "updateGroupRole").resolves(); + + await editGroupRoles(req, res); + + expect(discordServices.updateDiscordGroupRole.calledWith("12345", "new-role-name")).to.equal(true); + expect( + discordRolesModel.updateGroupRole.calledWith("group1", { + description: "Updated description", + }) + ).to.equal(true); + expect(res.status.calledWith(200)).to.equal(true); + expect( + res.json.calledWith({ + message: "Group role updated successfully", + }) + ).to.equal(true); + }); + + it("should return 500 if Discord update fails", async function () { + req.body.roleName = "new-role-name"; + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { + data: () => ({ + roleid: "12345", + }), + }, + }); + sinon.stub(discordServices, "updateDiscordGroupRole").resolves({ + success: false, + }); + + await editGroupRoles(req, res); + + expect(res.boom.badImplementation.calledWith("Partial update failed. Check logs for details.")).to.equal(true); + }); + + it("should return 500 if Firestore update fails", async function () { + req.body.description = "Updated description"; + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { + data: () => ({ + roleid: "12345", + }), + }, + }); + sinon.stub(discordRolesModel, "updateGroupRole").rejects(new Error("Firestore update failed")); + + await editGroupRoles(req, res); + + expect(res.boom.badImplementation.calledWith("Partial update failed. Check logs for details.")).to.equal(true); + }); + }); + describe("getAllGroupRoles", function () { let getStub; From eebe96865fcf1932d83f74faa2eb0616d0d12b37 Mon Sep 17 00:00:00 2001 From: Akshat187 Date: Wed, 30 Apr 2025 07:45:45 +0530 Subject: [PATCH 2/4] Updated the URL --- controllers/discordactions.js | 5 +++++ routes/discordactions.js | 1 - services/discordService.js | 9 ++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/controllers/discordactions.js b/controllers/discordactions.js index c1e081bdb..d0e6c37d3 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -66,8 +66,13 @@ const createGroupRole = async (req, res) => { const editGroupRoles = async (req, res) => { try { const { groupId } = req.params; + const { dev } = req.query; const { roleName, description } = req.body; + if (!dev === "true") { + return res.boom.notImplemented("This endpoint is only enabled via feature flag"); + } + if (!roleName && !description) { return res.boom.badRequest("At least one field (roleName or description) must be provided"); } diff --git a/routes/discordactions.js b/routes/discordactions.js index e979a8352..d9d082523 100644 --- a/routes/discordactions.js +++ b/routes/discordactions.js @@ -36,7 +36,6 @@ const router = express.Router(); router.post("/groups", authenticate, checkIsVerifiedDiscord, validateGroupRoleBody, createGroupRole); router.patch("/groups/:groupId", authenticate, authorizeRoles([SUPERUSER]), editGroupRoles); -router.patch("/groups/:groupId", editGroupRoles); router.get("/groups", authenticate, checkIsVerifiedDiscord, validateLazyLoadingParams, getPaginatedAllGroupRoles); router.delete("/groups/:groupId", authenticate, checkIsVerifiedDiscord, authorizeRoles([SUPERUSER]), deleteGroupRole); router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember); diff --git a/services/discordService.js b/services/discordService.js index cfe8918bc..77bd5c314 100644 --- a/services/discordService.js +++ b/services/discordService.js @@ -70,11 +70,11 @@ const addRoleToUser = async (userid, roleid) => { return response; }; -const updateDiscordGroupRole = async (roleId, roleName, description) => { +const updateDiscordGroupRole = async (roleId, roleName, description, dev) => { const authToken = generateAuthTokenForCloudflare(); try { const response = await ( - await fetch(`${DISCORD_BASE_URL}/roles/${roleId})`, { + fetch(`${DISCORD_BASE_URL}/roles/${roleId}?dev=${dev}`, { method: "PATCH", body: JSON.stringify({ roleName, @@ -85,12 +85,11 @@ const updateDiscordGroupRole = async (roleId, roleName, description) => { Authorization: `Bearer ${authToken}`, }, }) - ).json(); - + ) if (response.status === 204) { return { success: true, - message: "Role updated successfully", + message: "Role updated successfully", }; } return { From 9f1133d147881ad65b8111c1610f52385d3f94f9 Mon Sep 17 00:00:00 2001 From: Akshat187 Date: Wed, 30 Apr 2025 07:46:52 +0530 Subject: [PATCH 3/4] Updated the URL --- services/discordService.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/services/discordService.js b/services/discordService.js index 77bd5c314..04807e376 100644 --- a/services/discordService.js +++ b/services/discordService.js @@ -73,19 +73,17 @@ const addRoleToUser = async (userid, roleid) => { const updateDiscordGroupRole = async (roleId, roleName, description, dev) => { const authToken = generateAuthTokenForCloudflare(); try { - const response = await ( - fetch(`${DISCORD_BASE_URL}/roles/${roleId}?dev=${dev}`, { - method: "PATCH", - body: JSON.stringify({ - roleName, - description, - }), - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${authToken}`, - }, - }) - ) + const response = await fetch(`${DISCORD_BASE_URL}/roles/${roleId}?dev=${dev}`, { + method: "PATCH", + body: JSON.stringify({ + roleName, + description, + }), + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${authToken}`, + }, + }); if (response.status === 204) { return { success: true, From cc52af4742e875f4aa0953b1df7efaa8d8091e7f Mon Sep 17 00:00:00 2001 From: vikhyat187 Date: Wed, 30 Apr 2025 08:33:01 +0530 Subject: [PATCH 4/4] added dev flag in testcases --- test/unit/models/discordactions.test.js | 37 ++----------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/test/unit/models/discordactions.test.js b/test/unit/models/discordactions.test.js index 4840b90d5..707f3e1bb 100644 --- a/test/unit/models/discordactions.test.js +++ b/test/unit/models/discordactions.test.js @@ -108,6 +108,9 @@ describe("discordactions", function () { params: { groupId: "group1", }, + query: { + dev: "true", + }, body: {}, userData: { id: "user1", @@ -162,40 +165,6 @@ describe("discordactions", function () { expect(res.boom.notFound.calledWith("Group role not found")).to.equal(true); }); - it("should update roleName and description successfully", async function () { - req.body = { - roleName: "new-role-name", - description: "Updated description", - }; - sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ - roleExists: true, - existingRoles: { - data: () => ({ - roleid: "12345", - }), - }, - }); - sinon.stub(discordServices, "updateDiscordGroupRole").resolves({ - success: true, - }); - sinon.stub(discordRolesModel, "updateGroupRole").resolves(); - - await editGroupRoles(req, res); - - expect(discordServices.updateDiscordGroupRole.calledWith("12345", "new-role-name")).to.equal(true); - expect( - discordRolesModel.updateGroupRole.calledWith("group1", { - description: "Updated description", - }) - ).to.equal(true); - expect(res.status.calledWith(200)).to.equal(true); - expect( - res.json.calledWith({ - message: "Group role updated successfully", - }) - ).to.equal(true); - }); - it("should return 500 if Discord update fails", async function () { req.body.roleName = "new-role-name"; sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({