Skip to content

Commit 2f9b5f3

Browse files
authored
Member data validation (#70)
* added controller to get user details * added endpoint for getting discord member details * added new test * changed user-id to discord-id
1 parent 9de6ce0 commit 2f9b5f3

File tree

6 files changed

+185
-0
lines changed

6 files changed

+185
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as response from "../constants/responses";
2+
import { env } from "../typeDefinitions/default.types";
3+
import JSONResponse from "../utils/JsonResponse";
4+
import { IRequest } from "itty-router";
5+
import { verifyAuthToken } from "../utils/verifyAuthToken";
6+
import { getGuildMemberDetails } from "../utils/getGuildMemberDetails";
7+
8+
export async function getGuildMemberDetailsHandler(
9+
request: IRequest,
10+
env: env
11+
) {
12+
const authHeader = request.headers.get("Authorization");
13+
if (!authHeader) {
14+
return new JSONResponse(response.BAD_SIGNATURE);
15+
}
16+
try {
17+
await verifyAuthToken(authHeader, env);
18+
19+
const { id: discordId } = request.params;
20+
21+
const res = await getGuildMemberDetails(discordId, env);
22+
return new JSONResponse(res);
23+
} catch (err) {
24+
return new JSONResponse(response.BAD_SIGNATURE);
25+
}
26+
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "./controllers/guildRoleHandler";
1313
import { getMembersInServerHandler } from "./controllers/getMembersInServer";
1414
import { changeNickname } from "./controllers/changeNickname";
15+
import { getGuildMemberDetailsHandler } from "./controllers/getGuildMemberDetailsHandler";
1516

1617
const router = Router();
1718

@@ -29,6 +30,8 @@ router.put("/roles/add", addGroupRoleHandler);
2930

3031
router.get("/discord-members", getMembersInServerHandler);
3132

33+
router.get("/member/:id", getGuildMemberDetailsHandler);
34+
3235
router.post("/", async (request, env) => {
3336
const message: discordMessageRequest = await request.json();
3437
if (message.type === InteractionType.PING) {

src/typeDefinitions/discordMessage.types.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,30 @@ export interface guildRoleResponse {
5353
mentionable: boolean;
5454
tags?: object;
5555
}
56+
57+
export interface discordMemberDetails {
58+
avatar: string;
59+
communication_disabled_until: string;
60+
flags: number;
61+
joined_at: string;
62+
nick: string;
63+
pending: boolean;
64+
premium_since: string;
65+
roles: Array<string>;
66+
user: {
67+
id: string;
68+
username: string;
69+
avatar: string;
70+
discriminator: string;
71+
public_flags: number;
72+
flags: number;
73+
banner: string;
74+
accent_color: string;
75+
global_name: string;
76+
avatar_decoration: string;
77+
display_name: string;
78+
banner_color: string;
79+
};
80+
mute: boolean;
81+
deaf: boolean;
82+
}

src/utils/getGuildMemberDetails.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { INTERNAL_SERVER_ERROR } from "../constants/responses";
2+
import { DISCORD_BASE_URL } from "../constants/urls";
3+
import { env } from "../typeDefinitions/default.types";
4+
import { discordMemberDetails } from "../typeDefinitions/discordMessage.types";
5+
6+
export async function getGuildMemberDetails(
7+
discordId: string,
8+
env: env
9+
): Promise<discordMemberDetails | string> {
10+
const getGuildMemberDetailsUrl = `${DISCORD_BASE_URL}/guilds/${env.DISCORD_GUILD_ID}/members/${discordId}`;
11+
try {
12+
const response = await fetch(getGuildMemberDetailsUrl, {
13+
method: "GET",
14+
headers: {
15+
"Content-Type": "application/json",
16+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
17+
},
18+
});
19+
if (response.ok) {
20+
return await response.json();
21+
} else {
22+
return INTERNAL_SERVER_ERROR;
23+
}
24+
} catch (err) {
25+
return INTERNAL_SERVER_ERROR;
26+
}
27+
}

tests/fixtures/fixture.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,30 @@ export const guildEnv = {
5151
DISCORD_GUILD_ID: "1234",
5252
DISCORD_TOKEN: "abcd",
5353
};
54+
55+
export const dummyGuildMemberDetails = {
56+
avatar: null,
57+
communication_disabled_until: null,
58+
flags: 0,
59+
joined_at: "2023-04-01T01:00:09.204000+00:00",
60+
nick: null,
61+
pending: false,
62+
premium_since: null,
63+
roles: [],
64+
user: {
65+
id: "12345678",
66+
username: "John Doe",
67+
avatar: "123abc123xyz",
68+
discriminator: "6818",
69+
public_flags: 64,
70+
flags: 64,
71+
banner: null,
72+
accent_color: 16721920,
73+
global_name: null,
74+
avatar_decoration: null,
75+
display_name: null,
76+
banner_color: "#ff2800",
77+
},
78+
mute: false,
79+
deaf: false,
80+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { DISCORD_BASE_URL } from "../../../src/constants/urls";
2+
import { getGuildMemberDetails } from "../../../src/utils/getGuildMemberDetails";
3+
import { dummyGuildMemberDetails } from "../../fixtures/fixture";
4+
5+
describe("getGuildMemberDetails", () => {
6+
const mockEnv = {
7+
BOT_PUBLIC_KEY: "xyz",
8+
DISCORD_GUILD_ID: "123",
9+
DISCORD_TOKEN: "abc",
10+
};
11+
const mockUserId = "12345678";
12+
const GET_GUILD_MEMBER_URL = `${DISCORD_BASE_URL}/guilds/${mockEnv.DISCORD_GUILD_ID}/members/${mockUserId}`;
13+
beforeEach(() => {
14+
jest.resetAllMocks();
15+
});
16+
17+
test("returns member details on success", async () => {
18+
jest.spyOn(global, "fetch").mockResolvedValueOnce({
19+
ok: true,
20+
json: jest.fn().mockResolvedValueOnce(dummyGuildMemberDetails),
21+
} as unknown as Response);
22+
23+
const result = await getGuildMemberDetails(mockUserId, mockEnv);
24+
25+
expect(global.fetch).toHaveBeenCalledWith(GET_GUILD_MEMBER_URL, {
26+
method: "GET",
27+
headers: {
28+
"Content-Type": "application/json",
29+
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
30+
},
31+
});
32+
33+
expect(result).toEqual(dummyGuildMemberDetails);
34+
});
35+
36+
test("returns INTERNAL_SERVER_ERROR on non-ok response", async () => {
37+
jest.spyOn(global, "fetch").mockResolvedValueOnce({
38+
ok: false,
39+
} as unknown as Response);
40+
41+
const result = await getGuildMemberDetails(mockUserId, mockEnv);
42+
43+
expect(global.fetch).toHaveBeenCalledWith(GET_GUILD_MEMBER_URL, {
44+
method: "GET",
45+
headers: {
46+
"Content-Type": "application/json",
47+
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
48+
},
49+
});
50+
51+
expect(result).toEqual(
52+
"Oops! We have encountered an internal Server Error"
53+
);
54+
});
55+
56+
test("returns INTERNAL_SERVER_ERROR on fetch error", async () => {
57+
jest
58+
.spyOn(global, "fetch")
59+
.mockRejectedValueOnce(new Error("Network error"));
60+
61+
const result = await getGuildMemberDetails(mockUserId, mockEnv);
62+
63+
expect(global.fetch).toHaveBeenCalledWith(GET_GUILD_MEMBER_URL, {
64+
method: "GET",
65+
headers: {
66+
"Content-Type": "application/json",
67+
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
68+
},
69+
});
70+
71+
expect(result).toEqual(
72+
"Oops! We have encountered an internal Server Error"
73+
);
74+
});
75+
});

0 commit comments

Comments
 (0)