Skip to content

Commit d2ce324

Browse files
Adds route and controller for multiple user role updates (RealDevSquad#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 <[email protected]>
1 parent 6ea6973 commit d2ce324

File tree

5 files changed

+210
-0
lines changed

5 files changed

+210
-0
lines changed

src/constants/requestsActions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const GROUP_ROLE_ADD = {
2+
ADD_ROLE: "add-role",
3+
};

src/controllers/guildRoleHandler.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
memberGroupRole,
1515
} from "../typeDefinitions/discordMessage.types";
1616
import { verifyAuthToken } from "../utils/verifyAuthToken";
17+
import { batchDiscordRequests } from "../utils/batchDiscordRequests";
18+
import { DISCORD_BASE_URL } from "../constants/urls";
19+
import { GROUP_ROLE_ADD } from "../constants/requestsActions";
1720

1821
export async function createGuildRoleHandler(request: IRequest, env: env) {
1922
const authHeader = request.headers.get("Authorization");
@@ -46,6 +49,96 @@ export async function addGroupRoleHandler(request: IRequest, env: env) {
4649
}
4750
}
4851

52+
export async function getGuildRolesPostHandler(request: IRequest, env: env) {
53+
const authHeader = request.headers.get("Authorization");
54+
if (!authHeader) {
55+
return new JSONResponse(response.BAD_SIGNATURE);
56+
}
57+
58+
try {
59+
await verifyAuthToken(authHeader, env);
60+
const { action } = request.query;
61+
62+
switch (action) {
63+
case GROUP_ROLE_ADD.ADD_ROLE: {
64+
const memberGroupRoleList = await request.json();
65+
const res = await bulkAddGroupRoleHandler(memberGroupRoleList, env);
66+
return res;
67+
}
68+
default: {
69+
return new JSONResponse(response.BAD_SIGNATURE);
70+
}
71+
}
72+
} catch (err) {
73+
console.error(err);
74+
return new JSONResponse(response.INTERNAL_SERVER_ERROR);
75+
}
76+
}
77+
78+
export async function bulkAddGroupRoleHandler(
79+
memberGroupRoleList: memberGroupRole[],
80+
env: env
81+
): Promise<JSONResponse> {
82+
try {
83+
if (!Array.isArray(memberGroupRoleList)) {
84+
return new JSONResponse(response.BAD_SIGNATURE, {
85+
status: 400,
86+
statusText: "Expecting an array for user id and role id as payload",
87+
});
88+
}
89+
if (memberGroupRoleList.length < 1) {
90+
return new JSONResponse(response.BAD_SIGNATURE, {
91+
status: 400,
92+
statusText: "Minimum length of request is 1",
93+
});
94+
}
95+
if (memberGroupRoleList.length > 25) {
96+
return new JSONResponse(response.BAD_SIGNATURE, {
97+
status: 400,
98+
statusText: "Max requests length is 25",
99+
});
100+
}
101+
102+
const addGroupRoleRequests = [];
103+
for (const memberGroupRole of memberGroupRoleList) {
104+
const addRoleRequest = async () => {
105+
const { userid, roleid } = memberGroupRole;
106+
try {
107+
const createGuildRoleUrl = `${DISCORD_BASE_URL}/guilds/${env.DISCORD_GUILD_ID}/members/${userid}/roles/${roleid}`;
108+
const options = {
109+
method: "PUT",
110+
headers: {
111+
"Content-Type": "application/json",
112+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
113+
},
114+
};
115+
return await fetch(createGuildRoleUrl, options);
116+
} catch (error) {
117+
console.error(
118+
`Error occurred while trying to add role: ${roleid} to user: ${userid}`,
119+
error
120+
);
121+
throw error;
122+
}
123+
};
124+
addGroupRoleRequests.push(addRoleRequest);
125+
}
126+
const responseList = await batchDiscordRequests(addGroupRoleRequests);
127+
128+
const responseBody = memberGroupRoleList.map((memberGroupRole, index) => {
129+
return {
130+
userid: memberGroupRole.userid,
131+
roleid: memberGroupRole.roleid,
132+
success: responseList[index].ok,
133+
};
134+
});
135+
return new JSONResponse(responseBody);
136+
} catch (e) {
137+
console.error(e);
138+
throw e;
139+
}
140+
}
141+
49142
export async function removeGuildRoleHandler(request: IRequest, env: env) {
50143
const authHeader = request.headers.get("Authorization");
51144
if (!authHeader) {

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
removeGuildRoleHandler,
1313
getGuildRoleByRoleNameHandler,
1414
getGuildRolesHandler,
15+
getGuildRolesPostHandler,
1516
} from "./controllers/guildRoleHandler";
1617
import { getMembersInServerHandler } from "./controllers/getMembersInServer";
1718
import { changeNickname } from "./controllers/changeNickname";
@@ -33,6 +34,8 @@ router.put("/roles/create", createGuildRoleHandler);
3334

3435
router.put("/roles/add", addGroupRoleHandler);
3536

37+
router.post("/roles", getGuildRolesPostHandler);
38+
3639
router.delete("/roles", removeGuildRoleHandler);
3740

3841
router.get("/roles", getGuildRolesHandler);

tests/fixtures/fixture.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,15 @@ export const userFutureStatusMock: UserStatus = {
248248
},
249249
message: "User Status found successfully.",
250250
};
251+
252+
export const memberGroupRoleList: memberGroupRole[] = [
253+
{ userid: "XXXX", roleid: "XXXX" },
254+
{ userid: "YYYY", roleid: "YYYY" },
255+
{ userid: "ZZZZ", roleid: "ZZZZ" },
256+
];
257+
258+
export const memberGroupRoleResponseList = [
259+
{ userid: "XXXX", roleid: "XXXX", success: true },
260+
{ userid: "YYYY", roleid: "YYYY", success: true },
261+
{ userid: "ZZZZ", roleid: "ZZZZ", success: true },
262+
];

tests/unit/handlers/guildRoleHandler.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import {
22
getGuildRoleByRoleNameHandler,
33
getGuildRolesHandler,
4+
getGuildRolesPostHandler,
45
} from "../../../src/controllers/guildRoleHandler";
56
import { Role } from "../../../src/typeDefinitions/role.types";
67
import JSONResponse from "../../../src/utils/JsonResponse";
78
import {
89
generateDummyRequestObject,
910
guildEnv,
11+
memberGroupRoleList,
12+
memberGroupRoleResponseList,
1013
rolesMock,
1114
} from "../../fixtures/fixture";
1215
import * as responseConstants from "../../../src/constants/responses";
1316
import * as guildRoleUtils from "../../../src/utils/guildRole";
17+
import { GROUP_ROLE_ADD } from "../../../src/constants/requestsActions";
1418

1519
jest.mock("../../../src/utils/verifyAuthToken", () => ({
1620
verifyAuthToken: jest.fn().mockReturnValue(true),
@@ -247,3 +251,98 @@ describe("get role by role name", () => {
247251
expect(role).toEqual(resultMock);
248252
});
249253
});
254+
255+
describe("getGuildRolesPostHandler", () => {
256+
beforeEach(() => {
257+
jest.spyOn(global, "fetch").mockImplementation(
258+
() =>
259+
new Promise((resolve) => {
260+
return resolve(new JSONResponse({}, { status: 200 }));
261+
})
262+
);
263+
});
264+
265+
afterEach(() => {
266+
jest.resetAllMocks();
267+
jest.restoreAllMocks();
268+
});
269+
270+
it("should return response with user id and status for bulk add group roles", async () => {
271+
const mockRequest = generateDummyRequestObject({
272+
url: "/roles",
273+
method: "POST",
274+
headers: { Authorization: "Bearer testtoken" },
275+
json: () => Promise.resolve(memberGroupRoleList),
276+
query: { action: GROUP_ROLE_ADD.ADD_ROLE },
277+
});
278+
const response = await getGuildRolesPostHandler(mockRequest, guildEnv);
279+
expect(response).toBeInstanceOf(JSONResponse);
280+
const responseBody = await response.json();
281+
expect(responseBody).toEqual(memberGroupRoleResponseList);
282+
});
283+
284+
it("should return Bad Signature object if no auth headers provided", async () => {
285+
const mockRequest = generateDummyRequestObject({
286+
url: "/roles",
287+
method: "POST",
288+
json: () => Promise.resolve(memberGroupRoleList),
289+
query: { action: GROUP_ROLE_ADD.ADD_ROLE },
290+
});
291+
const response: JSONResponse = await getGuildRolesPostHandler(
292+
mockRequest,
293+
guildEnv
294+
);
295+
const jsonResponse: { error: string } = await response.json();
296+
expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE);
297+
});
298+
299+
it("should return Bad Signature object if invalid action is provided", async () => {
300+
const mockRequest = generateDummyRequestObject({
301+
url: "/roles",
302+
method: "POST",
303+
headers: { Authorization: "Bearer testtoken" },
304+
json: () => Promise.resolve(memberGroupRoleList),
305+
query: { action: "INVALID_ACTION" },
306+
});
307+
const response: JSONResponse = await getGuildRolesPostHandler(
308+
mockRequest,
309+
guildEnv
310+
);
311+
const jsonResponse: { error: string } = await response.json();
312+
expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE);
313+
});
314+
315+
it("should return Bad Signature if request body length is above 25", async () => {
316+
const requestList = new Array(26).fill(memberGroupRoleList[0]);
317+
const mockRequest = generateDummyRequestObject({
318+
url: "/roles",
319+
method: "POST",
320+
headers: { Authorization: "Bearer testtoken" },
321+
json: () => Promise.resolve(requestList),
322+
query: { action: GROUP_ROLE_ADD.ADD_ROLE },
323+
});
324+
const response: JSONResponse = await getGuildRolesPostHandler(
325+
mockRequest,
326+
guildEnv
327+
);
328+
const jsonResponse: { error: string } = await response.json();
329+
expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE);
330+
expect(response.statusText).toBe("Max requests length is 25");
331+
});
332+
333+
it("should return internal server error when theres an error", async () => {
334+
const mockRequest = generateDummyRequestObject({
335+
url: "/roles",
336+
method: "POST",
337+
headers: { Authorization: "Bearer testtoken" },
338+
json: [],
339+
query: { action: GROUP_ROLE_ADD.ADD_ROLE },
340+
});
341+
const response: JSONResponse = await getGuildRolesPostHandler(
342+
mockRequest,
343+
guildEnv
344+
);
345+
const jsonResponse: { error: string } = await response.json();
346+
expect(jsonResponse).toEqual(responseConstants.INTERNAL_SERVER_ERROR);
347+
});
348+
});

0 commit comments

Comments
 (0)