Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
STAGING_RDS_TRACKING_CHANNEL_URL,
RDS_STATUS_SITE_URL,
RDS_STAGING_STATUS_SITE_URL,
RDS_DASHBOARD_SITE_URL,
RDS_STAGING_DASHBOARD_SITE_URL,
} from "../src/constants/urls";
import {
DISCORD_PROFILE_SERVICE_HELP_GROUP,
Expand All @@ -26,13 +28,15 @@ const config = (env: env) => {
TRACKING_CHANNEL_URL: RDS_TRACKING_CHANNEL_URL,
PROFILE_SERVICE_HELP_GROUP_ID: DISCORD_PROFILE_SERVICE_HELP_GROUP,
RDS_STATUS_SITE_URL: RDS_STATUS_SITE_URL,
DASHBOARD_SITE_URL: RDS_DASHBOARD_SITE_URL,
},
staging: {
RDS_BASE_API_URL: RDS_BASE_STAGING_API_URL,
VERIFICATION_SITE_URL: STAGING_VERIFICATION_SITE_URL,
TRACKING_CHANNEL_URL: STAGING_RDS_TRACKING_CHANNEL_URL,
PROFILE_SERVICE_HELP_GROUP_ID: DISCORD_PROFILE_SERVICE_STAGING_HELP_GROUP,
RDS_STATUS_SITE_URL: RDS_STAGING_STATUS_SITE_URL,
DASHBOARD_SITE_URL: RDS_STAGING_DASHBOARD_SITE_URL,
},
default: {
RDS_BASE_API_URL: RDS_BASE_DEVELOPMENT_API_URL,
Expand All @@ -41,6 +45,7 @@ const config = (env: env) => {
PROFILE_SERVICE_HELP_GROUP_ID:
DISCORD_PROFILE_SERVICE_DEVELOPMENT_HELP_GROUP,
RDS_STATUS_SITE_URL: RDS_STATUS_SITE_URL,
DASHBOARD_SITE_URL: RDS_DASHBOARD_SITE_URL,
},
};

Expand Down
19 changes: 19 additions & 0 deletions src/constants/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ export const VERIFY = {
"Generate a link with user specific token to link with RDS backend.",
};

export const GROUP_INVITE = {
name: "group-invite",
description: "Send group invite link for the user.",
options: [
{
name: "name",
description: "User to send group invite link",
type: 6,
required: true,
},
{
name: "role",
description: "Role you want to invite to the user",
type: 8,
required: true,
},
],
};

export const MENTION_EACH = {
name: "mention-each",
description: "mention each user with this role",
Expand Down
3 changes: 3 additions & 0 deletions src/constants/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export const FAILED_TO_FETCH_TASKS = `Failed to fetch tasks for **{{assignee}}**
export const USER_NOT_FOUND = `User Not Found`;
export const USER_STATUS_NOT_FOUND = "No Status Found";

export const FAILED_TO_FETCH_DISCORD_GUILD_ROLE =
"Failed to fetch discord guild role";

export const OVERDUE_DEFAULT_MESSAGE = "You have overdue tasks.";
export const OVERDUE_CUSTOM_MESSAGE =
"Please be aware that you currently have tasks that are overdue or due within the next {{days}} day. If you require additional time to complete these tasks, kindly submit an extension request.";
Expand Down
4 changes: 4 additions & 0 deletions src/constants/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ export const DEVELOPMENT_RDS_TRACKING_CHANNEL_URL =
export const RDS_STATUS_SITE_URL = "https://status.realdevsquad.com";
export const RDS_STAGING_STATUS_SITE_URL =
"https://staging-status.realdevsquad.com";

export const RDS_DASHBOARD_SITE_URL = "https://dashboard.realdevsquad.com";
export const RDS_STAGING_DASHBOARD_SITE_URL =
"https://staging-dashboard.realdevsquad.com";
8 changes: 8 additions & 0 deletions src/controllers/baseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
OOO,
USER,
REMOVE,
GROUP_INVITE,
} from "../constants/commands";
import { updateNickName } from "../utils/updateNickname";
import { discordEphemeralResponse } from "../utils/discordEphemeralResponse";
Expand All @@ -42,6 +43,7 @@ import {
} from "../constants/responses";
import { DevFlag } from "../typeDefinitions/filterUsersByRole";
import { kickEachUser } from "./kickEachUser";
import { groupInvite } from "./groupInvite";

export async function baseHandler(
message: discordMessageRequest,
Expand Down Expand Up @@ -159,6 +161,12 @@ export async function baseHandler(
) as unknown as DevFlag;
return await userCommand(data[0].value, env, dev);
}

case getCommandName(GROUP_INVITE): {
const data = message.data?.options as Array<messageRequestDataOptions>;

return await groupInvite(data[0].value, data[1].value, env);
}
default: {
return commandNotFound();
}
Expand Down
26 changes: 26 additions & 0 deletions src/controllers/groupInvite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import config from "../../config/config";
import { env } from "../typeDefinitions/default.types";
import { discordTextResponse } from "../utils/discordResponse";
import * as DiscordGroups from "../utils/fetchDiscordGroupById";
import JSONResponse from "../utils/JsonResponse";

export async function groupInvite(
userId: string,
roleId: string,
env: env
): Promise<JSONResponse> {
const group = await DiscordGroups.fetchDiscordGroupById(roleId, env);

if (!group.name.startsWith("group-")) {
return discordTextResponse(`<@&${roleId}> is not a valid group.`);
}

const groupName = group.name.replace(/^group-/, "").replace(/-/g, " ");
const encodedGroupName = encodeURIComponent(groupName);

return discordTextResponse(
`<@${userId}> join the group <@&${roleId}> via the link below:\n ${
config(env).DASHBOARD_SITE_URL
}/groups/?dev=true&name=${encodedGroupName}`
);
}
8 changes: 5 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ router.get("/ankush", async (request, env, ctx: ExecutionContext) => {
ctx.waitUntil(send(env));

const url = config(env).TRACKING_CHANNEL_URL;

return new JSONResponse(`CURRENT_ENVIRONMENT: ${env.CURRENT_ENVIRONMENT}, tracking url - ${url}`, { status: 200 });
});

return new JSONResponse(
`CURRENT_ENVIRONMENT: ${env.CURRENT_ENVIRONMENT}, tracking url - ${url}`,
{ status: 200 }
);
});

router.post("/", async (request, env, ctx: ExecutionContext) => {
const message: discordMessageRequest = await request.json();
Expand Down
2 changes: 2 additions & 0 deletions src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
OOO,
USER,
REMOVE,
GROUP_INVITE,
} from "./constants/commands";
import { config } from "dotenv";
import { DISCORD_BASE_URL } from "./constants/urls";
Expand Down Expand Up @@ -39,6 +40,7 @@ async function registerGuildCommands(
NOTIFY_OVERDUE,
NOTIFY_ONBOARDING,
REMOVE,
GROUP_INVITE,
];

try {
Expand Down
1 change: 1 addition & 0 deletions src/typeDefinitions/default.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface environment {
}

export interface variables {
DASHBOARD_SITE_URL: string;
RDS_BASE_API_URL: string;
VERIFICATION_SITE_URL: string;
TRACKING_CHANNEL_URL: string;
Expand Down
4 changes: 4 additions & 0 deletions src/typeDefinitions/group.types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type DiscordGuildRole = {
name: string;
id: string;
};
34 changes: 34 additions & 0 deletions src/utils/fetchDiscordGroupById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FAILED_TO_FETCH_DISCORD_GUILD_ROLE } from "../constants/responses";
import { DISCORD_BASE_URL } from "../constants/urls";
import { env } from "../typeDefinitions/default.types";
import { DiscordGuildRole } from "../typeDefinitions/group.types";
import createDiscordHeaders from "./createDiscordHeaders";

async function fetchDiscordGroupById(
roleId: string,
env: env
): Promise<DiscordGuildRole> {
try {
const url = `${DISCORD_BASE_URL}/guilds/${env.DISCORD_GUILD_ID}/roles/${roleId}`;
const headers: HeadersInit = createDiscordHeaders({
token: env.DISCORD_TOKEN,
});
const options = {
method: "GET",
headers,
};
const response = await fetch(url, options);

if (!response.ok) {
throw new Error(FAILED_TO_FETCH_DISCORD_GUILD_ROLE);
}

const responseData: DiscordGuildRole = await response.json();
return responseData;
} catch (error) {
console.error("An error occurred while fetching discord groups:", error);
throw error;
}
}

export { fetchDiscordGroupById };
46 changes: 37 additions & 9 deletions src/utils/formatUserDetails.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UserResponseType } from "../typeDefinitions/rdsUser";

export function convertTimeStamp(userDetails: UserResponseType) {
export function convertTimeStamp(userDetails: UserResponseType, flag: boolean) {
const timestamp = userDetails.user?.discordJoinedAt;

if (timestamp) {
Expand All @@ -9,27 +9,55 @@ export function convertTimeStamp(userDetails: UserResponseType) {
const day = String(date.getDate()).padStart(2, "0");
const month = String(date.getMonth() + 1).padStart(2, "0");
const year = date.getFullYear();
if (flag) {
const monthName = date.toLocaleString("default", { month: "short" });
const formattedDate = `${day} ${monthName}, ${year}`;
return formattedDate;
}
const formattedDate = `${day}/${month}/${year}`;
return formattedDate;
}

return "N/A";
}
export function convertEpochToDate(timestamp: number | undefined) {
if (timestamp) {
const date = new Date(timestamp);

const day = String(date.getDate()).padStart(2, "0");
const month = date.toLocaleString("default", { month: "short" });
const year = date.getFullYear();
const formattedDate = `${day} ${month}, ${year}`;

return formattedDate;
}

return "N/A";
}

export function formatUserDetails(
userDetails: UserResponseType,
flag: boolean
) {
const convertedTimestamp = convertTimeStamp(userDetails);

const userId = `**User Id :** ${userDetails.user?.id}`;
const userName = `**User Name :** ${userDetails.user?.username}`;
const userFullName = `**Full Name :** ${userDetails.user?.first_name} ${userDetails.user?.last_name}`;
const userState = `**State :** ${userDetails.user?.state}`;
const discordJoinedAt = `**Joined Server on :** ${convertedTimestamp}`;
const convertedTimestamp = convertTimeStamp(userDetails, flag);
const accountCreationDate = convertEpochToDate(userDetails.user?.created_at);

const userId = " ".repeat(30) + `**Id:** ${userDetails.user?.id}`;
const userName =
" ".repeat(13) + `**Username:** ${userDetails.user?.username}`;
const userFullName = `${
flag ? " ".repeat(13) + "**Full Name:**" : "**Full Name :**"
} ${userDetails.user?.first_name} ${userDetails.user?.last_name}`;
const userState = `${flag ? " ".repeat(23) + "**State:**" : "**State :**"} ${
userDetails.user?.state
}`;
const discordJoinedAt = `${
flag ? " ".repeat(4) + "**Joined Discord:**" : "**Joined Server on :**"
} ${convertedTimestamp}`;
const accountCreated = `**Account Created:** ${accountCreationDate}`;

if (!flag)
return `## User Details\n${userFullName}\n${userState}\n${discordJoinedAt}`;

return `## User Details\n${userId}\n${userName}\n${userFullName}\n${userState}\n${discordJoinedAt}`;
return `## User Details\n${userName}\n${userState}\n\n${userFullName}\n${userId}\n\n${accountCreated}\n${discordJoinedAt}`;
}
11 changes: 11 additions & 0 deletions tests/fixtures/groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DiscordGuildRole } from "../../src/typeDefinitions/group.types";

export const group: DiscordGuildRole = {
id: "1",
name: "group-frontend",
};

export const invalidGroup: DiscordGuildRole = {
id: "2",
name: "invalidRole",
};
2 changes: 2 additions & 0 deletions tests/fixtures/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const user = {
id: "iODXB6ns8jaZB9p0XlBw",
incompleteUserDetails: false,
discordJoinedAt: "2023-08-08T11:40:42.522000+00:00",
created_at: 1692748800000,
discordId: "858838385330487336",
github_display_name: "John Doe",
updated_at: 1694888822719,
Expand Down Expand Up @@ -178,6 +179,7 @@ export const userWithoutDiscordJoinedAt = {
id: "DWcTUhbC5lRXfDjZRp06",
incompleteUserDetails: false,
discordJoinedAt: "",
created_at: 1692748800000,
discordId: "504855562094247953",
github_display_name: "John Doe",
updated_at: 1694888822719,
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/handlers/groupInvite.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { environment } from "../../fixtures/config";
import * as DiscordGroups from "../../../src/utils/fetchDiscordGroupById";
import JSONResponse from "../../../src/utils/JsonResponse";
import { groupInvite } from "../../../src/controllers/groupInvite";
import { group, invalidGroup } from "../../fixtures/groups";
import { discordTextResponse } from "../../../src/utils/discordResponse";

describe("Test /group-invite command", () => {
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});

it("Should be an instance of JSONResponse", async () => {
jest
.spyOn(DiscordGroups, "fetchDiscordGroupById")
.mockImplementation(() => Promise.resolve(group));

const response = await groupInvite("1", group.id, environment[0]);

expect(response).toBeInstanceOf(JSONResponse);
});

it("Should return a discordTextResponse if group is not found", async () => {
jest
.spyOn(DiscordGroups, "fetchDiscordGroupById")
.mockImplementation(() => Promise.resolve(invalidGroup));

const expectedResponse = discordTextResponse(
`<@&${invalidGroup.id}> is not a valid group.`
);

const response = await groupInvite("1", invalidGroup.id, environment[0]);

expect(await response.json()).toEqual(await expectedResponse.json());
});
});
Loading
Loading