diff --git a/controllers/discordactions.js b/controllers/discordactions.js index f77758b00..d0e6c37d3 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -63,6 +63,80 @@ const createGroupRole = async (req, res) => { return res.boom.badImplementation(INTERNAL_SERVER_ERROR); } }; +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"); + } + + 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 +633,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..d9d082523 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,7 @@ 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.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..04807e376 100644 --- a/services/discordService.js +++ b/services/discordService.js @@ -70,6 +70,36 @@ const addRoleToUser = async (userid, roleid) => { return response; }; +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}`, + }, + }); + 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 +172,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..707f3e1bb 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,108 @@ describe("discordactions", function () { }); }); + describe("editGroupRoles", function () { + let req, res; + + beforeEach(function () { + req = { + params: { + groupId: "group1", + }, + query: { + dev: "true", + }, + 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 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;