From d2ce3246057c522b0a1e67319857b1397dd391ac Mon Sep 17 00:00:00 2001 From: Ajeyakrishna <98796547+Ajeyakrishna-k@users.noreply.github.com> Date: Wed, 13 Dec 2023 03:08:18 +0530 Subject: [PATCH] Adds route and controller for multiple user role updates (#166) * feat: discord bulk requests * feat: adds route and controller to bulk update discord roles * feat: update batch request function * chore: adds descriptive error messages and logs --------- Co-authored-by: Amit Prakash <34869115+iamitprakash@users.noreply.github.com> --- src/constants/requestsActions.ts | 3 + src/controllers/guildRoleHandler.ts | 93 ++++++++++++++++++ src/index.ts | 3 + tests/fixtures/fixture.ts | 12 +++ tests/unit/handlers/guildRoleHandler.test.ts | 99 ++++++++++++++++++++ 5 files changed, 210 insertions(+) create mode 100644 src/constants/requestsActions.ts diff --git a/src/constants/requestsActions.ts b/src/constants/requestsActions.ts new file mode 100644 index 00000000..95b78919 --- /dev/null +++ b/src/constants/requestsActions.ts @@ -0,0 +1,3 @@ +export const GROUP_ROLE_ADD = { + ADD_ROLE: "add-role", +}; diff --git a/src/controllers/guildRoleHandler.ts b/src/controllers/guildRoleHandler.ts index c52134e9..981b17f4 100644 --- a/src/controllers/guildRoleHandler.ts +++ b/src/controllers/guildRoleHandler.ts @@ -14,6 +14,9 @@ import { memberGroupRole, } from "../typeDefinitions/discordMessage.types"; import { verifyAuthToken } from "../utils/verifyAuthToken"; +import { batchDiscordRequests } from "../utils/batchDiscordRequests"; +import { DISCORD_BASE_URL } from "../constants/urls"; +import { GROUP_ROLE_ADD } from "../constants/requestsActions"; export async function createGuildRoleHandler(request: IRequest, env: env) { const authHeader = request.headers.get("Authorization"); @@ -46,6 +49,96 @@ export async function addGroupRoleHandler(request: IRequest, env: env) { } } +export async function getGuildRolesPostHandler(request: IRequest, env: env) { + const authHeader = request.headers.get("Authorization"); + if (!authHeader) { + return new JSONResponse(response.BAD_SIGNATURE); + } + + try { + await verifyAuthToken(authHeader, env); + const { action } = request.query; + + switch (action) { + case GROUP_ROLE_ADD.ADD_ROLE: { + const memberGroupRoleList = await request.json(); + const res = await bulkAddGroupRoleHandler(memberGroupRoleList, env); + return res; + } + default: { + return new JSONResponse(response.BAD_SIGNATURE); + } + } + } catch (err) { + console.error(err); + return new JSONResponse(response.INTERNAL_SERVER_ERROR); + } +} + +export async function bulkAddGroupRoleHandler( + memberGroupRoleList: memberGroupRole[], + env: env +): Promise { + try { + if (!Array.isArray(memberGroupRoleList)) { + return new JSONResponse(response.BAD_SIGNATURE, { + status: 400, + statusText: "Expecting an array for user id and role id as payload", + }); + } + if (memberGroupRoleList.length < 1) { + return new JSONResponse(response.BAD_SIGNATURE, { + status: 400, + statusText: "Minimum length of request is 1", + }); + } + if (memberGroupRoleList.length > 25) { + return new JSONResponse(response.BAD_SIGNATURE, { + status: 400, + statusText: "Max requests length is 25", + }); + } + + const addGroupRoleRequests = []; + for (const memberGroupRole of memberGroupRoleList) { + const addRoleRequest = async () => { + const { userid, roleid } = memberGroupRole; + try { + const createGuildRoleUrl = `${DISCORD_BASE_URL}/guilds/${env.DISCORD_GUILD_ID}/members/${userid}/roles/${roleid}`; + const options = { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bot ${env.DISCORD_TOKEN}`, + }, + }; + return await fetch(createGuildRoleUrl, options); + } catch (error) { + console.error( + `Error occurred while trying to add role: ${roleid} to user: ${userid}`, + error + ); + throw error; + } + }; + addGroupRoleRequests.push(addRoleRequest); + } + const responseList = await batchDiscordRequests(addGroupRoleRequests); + + const responseBody = memberGroupRoleList.map((memberGroupRole, index) => { + return { + userid: memberGroupRole.userid, + roleid: memberGroupRole.roleid, + success: responseList[index].ok, + }; + }); + return new JSONResponse(responseBody); + } catch (e) { + console.error(e); + throw e; + } +} + export async function removeGuildRoleHandler(request: IRequest, env: env) { const authHeader = request.headers.get("Authorization"); if (!authHeader) { diff --git a/src/index.ts b/src/index.ts index 5eaf430d..3f5cb8da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { removeGuildRoleHandler, getGuildRoleByRoleNameHandler, getGuildRolesHandler, + getGuildRolesPostHandler, } from "./controllers/guildRoleHandler"; import { getMembersInServerHandler } from "./controllers/getMembersInServer"; import { changeNickname } from "./controllers/changeNickname"; @@ -33,6 +34,8 @@ router.put("/roles/create", createGuildRoleHandler); router.put("/roles/add", addGroupRoleHandler); +router.post("/roles", getGuildRolesPostHandler); + router.delete("/roles", removeGuildRoleHandler); router.get("/roles", getGuildRolesHandler); diff --git a/tests/fixtures/fixture.ts b/tests/fixtures/fixture.ts index 5158220b..c56dceda 100644 --- a/tests/fixtures/fixture.ts +++ b/tests/fixtures/fixture.ts @@ -248,3 +248,15 @@ export const userFutureStatusMock: UserStatus = { }, message: "User Status found successfully.", }; + +export const memberGroupRoleList: memberGroupRole[] = [ + { userid: "XXXX", roleid: "XXXX" }, + { userid: "YYYY", roleid: "YYYY" }, + { userid: "ZZZZ", roleid: "ZZZZ" }, +]; + +export const memberGroupRoleResponseList = [ + { userid: "XXXX", roleid: "XXXX", success: true }, + { userid: "YYYY", roleid: "YYYY", success: true }, + { userid: "ZZZZ", roleid: "ZZZZ", success: true }, +]; diff --git a/tests/unit/handlers/guildRoleHandler.test.ts b/tests/unit/handlers/guildRoleHandler.test.ts index ad579564..cd7b89a5 100644 --- a/tests/unit/handlers/guildRoleHandler.test.ts +++ b/tests/unit/handlers/guildRoleHandler.test.ts @@ -1,16 +1,20 @@ import { getGuildRoleByRoleNameHandler, getGuildRolesHandler, + getGuildRolesPostHandler, } from "../../../src/controllers/guildRoleHandler"; import { Role } from "../../../src/typeDefinitions/role.types"; import JSONResponse from "../../../src/utils/JsonResponse"; import { generateDummyRequestObject, guildEnv, + memberGroupRoleList, + memberGroupRoleResponseList, rolesMock, } from "../../fixtures/fixture"; import * as responseConstants from "../../../src/constants/responses"; import * as guildRoleUtils from "../../../src/utils/guildRole"; +import { GROUP_ROLE_ADD } from "../../../src/constants/requestsActions"; jest.mock("../../../src/utils/verifyAuthToken", () => ({ verifyAuthToken: jest.fn().mockReturnValue(true), @@ -247,3 +251,98 @@ describe("get role by role name", () => { expect(role).toEqual(resultMock); }); }); + +describe("getGuildRolesPostHandler", () => { + beforeEach(() => { + jest.spyOn(global, "fetch").mockImplementation( + () => + new Promise((resolve) => { + return resolve(new JSONResponse({}, { status: 200 })); + }) + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + jest.restoreAllMocks(); + }); + + it("should return response with user id and status for bulk add group roles", async () => { + const mockRequest = generateDummyRequestObject({ + url: "/roles", + method: "POST", + headers: { Authorization: "Bearer testtoken" }, + json: () => Promise.resolve(memberGroupRoleList), + query: { action: GROUP_ROLE_ADD.ADD_ROLE }, + }); + const response = await getGuildRolesPostHandler(mockRequest, guildEnv); + expect(response).toBeInstanceOf(JSONResponse); + const responseBody = await response.json(); + expect(responseBody).toEqual(memberGroupRoleResponseList); + }); + + it("should return Bad Signature object if no auth headers provided", async () => { + const mockRequest = generateDummyRequestObject({ + url: "/roles", + method: "POST", + json: () => Promise.resolve(memberGroupRoleList), + query: { action: GROUP_ROLE_ADD.ADD_ROLE }, + }); + const response: JSONResponse = await getGuildRolesPostHandler( + mockRequest, + guildEnv + ); + const jsonResponse: { error: string } = await response.json(); + expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE); + }); + + it("should return Bad Signature object if invalid action is provided", async () => { + const mockRequest = generateDummyRequestObject({ + url: "/roles", + method: "POST", + headers: { Authorization: "Bearer testtoken" }, + json: () => Promise.resolve(memberGroupRoleList), + query: { action: "INVALID_ACTION" }, + }); + const response: JSONResponse = await getGuildRolesPostHandler( + mockRequest, + guildEnv + ); + const jsonResponse: { error: string } = await response.json(); + expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE); + }); + + it("should return Bad Signature if request body length is above 25", async () => { + const requestList = new Array(26).fill(memberGroupRoleList[0]); + const mockRequest = generateDummyRequestObject({ + url: "/roles", + method: "POST", + headers: { Authorization: "Bearer testtoken" }, + json: () => Promise.resolve(requestList), + query: { action: GROUP_ROLE_ADD.ADD_ROLE }, + }); + const response: JSONResponse = await getGuildRolesPostHandler( + mockRequest, + guildEnv + ); + const jsonResponse: { error: string } = await response.json(); + expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE); + expect(response.statusText).toBe("Max requests length is 25"); + }); + + it("should return internal server error when theres an error", async () => { + const mockRequest = generateDummyRequestObject({ + url: "/roles", + method: "POST", + headers: { Authorization: "Bearer testtoken" }, + json: [], + query: { action: GROUP_ROLE_ADD.ADD_ROLE }, + }); + const response: JSONResponse = await getGuildRolesPostHandler( + mockRequest, + guildEnv + ); + const jsonResponse: { error: string } = await response.json(); + expect(jsonResponse).toEqual(responseConstants.INTERNAL_SERVER_ERROR); + }); +});