Skip to content

Commit

Permalink
Adds route and controller for multiple user role updates (#166)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
Ajeyakrishna-k and iamitprakash authored Dec 12, 2023
1 parent 6ea6973 commit d2ce324
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/constants/requestsActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const GROUP_ROLE_ADD = {
ADD_ROLE: "add-role",
};
93 changes: 93 additions & 0 deletions src/controllers/guildRoleHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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<JSONResponse> {
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) {
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
removeGuildRoleHandler,
getGuildRoleByRoleNameHandler,
getGuildRolesHandler,
getGuildRolesPostHandler,
} from "./controllers/guildRoleHandler";
import { getMembersInServerHandler } from "./controllers/getMembersInServer";
import { changeNickname } from "./controllers/changeNickname";
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions tests/fixtures/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
];
99 changes: 99 additions & 0 deletions tests/unit/handlers/guildRoleHandler.test.ts
Original file line number Diff line number Diff line change
@@ -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),
Expand Down Expand Up @@ -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);
});
});

0 comments on commit d2ce324

Please sign in to comment.