diff --git a/api/src/discord/commands/linkUser.ts b/api/src/discord/commands/linkUser.ts index e47dd5a7c..f85542922 100644 --- a/api/src/discord/commands/linkUser.ts +++ b/api/src/discord/commands/linkUser.ts @@ -13,13 +13,15 @@ import { import { CTF, getAccessibleCTFsForUser } from "../database/ctfs"; import { getDiscordClient } from ".."; import config from "../../config"; +import { PoolClient } from "pg"; export async function changeDiscordUserRoleForCTF( userId: bigint, ctf: CTF | string | string[], - operation: "add" | "remove" + operation: "add" | "remove", + pgClient: PoolClient | null = null ): Promise { - const discordUserId = await getDiscordIdFromUserId(userId); + const discordUserId = await getDiscordIdFromUserId(userId, pgClient); if (discordUserId == null) return false; return changeDiscordUserRoleForCTFByDiscordId(discordUserId, ctf, operation); diff --git a/api/src/discord/database/ctfs.ts b/api/src/discord/database/ctfs.ts index d7b80572d..b740d8d43 100644 --- a/api/src/discord/database/ctfs.ts +++ b/api/src/discord/database/ctfs.ts @@ -14,6 +14,7 @@ export interface CTF { secrets_id: bigint; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function buildCtf(row: any): CTF { return { id: row.id as bigint, @@ -68,9 +69,11 @@ export async function getAllCtfsFromDatabase(): Promise { // get id from ctf name export async function getCtfFromDatabase( - ctfName: string | bigint + ctfName: string | bigint, + pgClient: PoolClient | null = null ): Promise { - const pgClient = await connectToDatabase(); + const useRequestClient = pgClient != null; + if (pgClient == null) pgClient = await connectToDatabase(); try { //make a query to get all the challenges from a ctf @@ -94,7 +97,7 @@ export async function getCtfFromDatabase( console.error("Failed to get CTF from the database:", error); return null; } finally { - pgClient.release(); + if (!useRequestClient) pgClient.release(); } } @@ -163,9 +166,11 @@ export async function getAccessibleCTFsForUser( // invite the user to play the CTF, but only if they don't have access yet export async function insertInvitation( ctfId: bigint, - profileId: bigint + profileId: bigint, + pgClient: PoolClient | null = null ): Promise { - const pgClient = await connectToDatabase(); + const useRequestClient = pgClient != null; + if (pgClient == null) pgClient = await connectToDatabase(); const accessibleCTFs = await getAccessibleCTFsForUser(profileId, pgClient); if (accessibleCTFs.find((ctf) => ctf.id === ctfId) != null) { @@ -182,12 +187,16 @@ export async function insertInvitation( console.error("Failed to insert invitation in the database:", error); return; } finally { - pgClient.release(); + if (!useRequestClient) pgClient.release(); } } -export async function getInvitedUsersByCtf(ctfId: bigint): Promise { - const pgClient = await connectToDatabase(); +export async function getInvitedUsersByCtf( + ctfId: bigint, + pgClient: PoolClient | null = null +): Promise { + const useRequestClient = pgClient != null; + if (pgClient == null) pgClient = await connectToDatabase(); try { const query = `SELECT profile_id FROM ctfnote.invitation WHERE ctf_id = $1`; @@ -199,15 +208,17 @@ export async function getInvitedUsersByCtf(ctfId: bigint): Promise { console.error("Failed to get invited users from the database:", error); return []; } finally { - pgClient.release(); + if (!useRequestClient) pgClient.release(); } } export async function deleteInvitation( ctfId: bigint, - profileId: bigint + profileId: bigint, + pgClient: PoolClient | null = null ): Promise { - const pgClient = await connectToDatabase(); + const useRequestClient = pgClient != null; + if (pgClient == null) pgClient = await connectToDatabase(); try { const query = `DELETE FROM ctfnote.invitation WHERE ctf_id = $1 AND profile_id = $2`; @@ -217,6 +228,6 @@ export async function deleteInvitation( console.error("Failed to delete invitation from the database:", error); return; } finally { - pgClient.release(); + if (!useRequestClient) pgClient.release(); } } diff --git a/api/src/discord/database/users.ts b/api/src/discord/database/users.ts index 7f2e91073..ecca8306f 100644 --- a/api/src/discord/database/users.ts +++ b/api/src/discord/database/users.ts @@ -1,4 +1,5 @@ import { connectToDatabase } from "./database"; +import { PoolClient } from "pg"; /* * Only returns users that have not linked their discord account yet. @@ -45,9 +46,11 @@ export async function setDiscordIdForUser( } export async function getUserByDiscordId( - discordId: string + discordId: string, + pgClient: PoolClient | null = null ): Promise { - const pgClient = await connectToDatabase(); + const useRequestClient = pgClient != null; + if (pgClient == null) pgClient = await connectToDatabase(); try { const query = @@ -59,14 +62,16 @@ export async function getUserByDiscordId( } catch (error) { return null; } finally { - pgClient.release(); + if (!useRequestClient) pgClient.release(); } } export async function getDiscordIdFromUserId( - userId: bigint + userId: bigint, + pgClient: PoolClient | null = null ): Promise { - const pgClient = await connectToDatabase(); + const useRequestClient = pgClient != null; + if (pgClient == null) pgClient = await connectToDatabase(); try { const query = "SELECT discord_id FROM ctfnote.profile WHERE id = $1"; const values = [userId]; @@ -76,7 +81,7 @@ export async function getDiscordIdFromUserId( } catch (error) { return null; } finally { - pgClient.release(); + if (!useRequestClient) pgClient.release(); } } diff --git a/api/src/discord/utils/permissionSync.ts b/api/src/discord/utils/permissionSync.ts index 5c2d5fcc4..06cb8a014 100644 --- a/api/src/discord/utils/permissionSync.ts +++ b/api/src/discord/utils/permissionSync.ts @@ -8,20 +8,28 @@ import { } from "../database/ctfs"; import { getDiscordIdFromUserId, getUserByDiscordId } from "../database/users"; import { changeDiscordUserRoleForCTF } from "../commands/linkUser"; +import { PoolClient } from "pg"; export async function syncDiscordPermissionsWithCtf( guild: Guild, ctfId: bigint, - discordLink: string | null + discordLink: string | null, + pgClient: PoolClient ) { - if (discordLink == null || discordLink.length == 0) return; - const ctf = await getCtfFromDatabase(ctfId); - if (ctf == null) return; + if (discordLink == null || discordLink.length == 0) { + return; + } + const ctf = await getCtfFromDatabase(ctfId, pgClient); + if (ctf == null) { + return; + } const guildEvents = await guild.scheduledEvents.fetch(); const eventId = discordLink.match(/event=(\d+)/)?.[1]; - if (eventId == null) return; + if (eventId == null) { + return; + } const event = guildEvents.get(eventId); if (event == null) return; @@ -31,13 +39,13 @@ export async function syncDiscordPermissionsWithCtf( const usersInterested = ( await Promise.all( discordUsersInterested.map(async function (user) { - return await getUserByDiscordId(user.user.id); + return await getUserByDiscordId(user.user.id, pgClient); }) ) ).filter((user) => user != null) as Array; // search for users that are invited to the CTF but are not interested in the event - const invitedUsers = await getInvitedUsersByCtf(ctfId); + const invitedUsers = await getInvitedUsersByCtf(ctfId, pgClient); const usersNotInterested = invitedUsers.filter( (user) => !usersInterested.includes(user) ); @@ -46,25 +54,28 @@ export async function syncDiscordPermissionsWithCtf( await Promise.all( usersNotInterested.map(async function (user) { if (user == null) return; - if ((await getDiscordIdFromUserId(user)) == null) return; // don't remove permissions through the Discord sync if the user doesn't have Discord linked - await deleteInvitation(ctfId, user); + if ((await getDiscordIdFromUserId(user, pgClient)) == null) return; // don't remove permissions through the Discord sync if the user doesn't have Discord linked + await deleteInvitation(ctfId, user, pgClient); // we just removed the invitation so we expect the user to not have access to the CTF anymore, // however if the user has member privileges or higher we do not remove the role because the user should still have access - const accessibleCTFs = await getAccessibleCTFsForUser(user); - if (accessibleCTFs.find((ctf) => ctf.id === ctfId) != null) return; + const accessibleCTFs = await getAccessibleCTFsForUser(user, pgClient); + if (accessibleCTFs.find((ctf) => ctf.id === ctfId) != null) { + return; + } - changeDiscordUserRoleForCTF(user, ctf, "remove"); + changeDiscordUserRoleForCTF(user, ctf, "remove", pgClient); }) ); // invite the users that are interested in the event but do not have access yet to the CTF - await Promise.all( + return await Promise.all( usersInterested.map(async function (user) { if (user == null) return; - insertInvitation(ctfId, user); + + insertInvitation(ctfId, user, pgClient); // we only add the role if the user also exists in CTFNote and therefore prevent that users only have a Discord account but no CTFNote account - changeDiscordUserRoleForCTF(user, ctf, "add"); + changeDiscordUserRoleForCTF(user, ctf, "add", pgClient); }) ); } diff --git a/api/src/plugins/discordHooks.ts b/api/src/plugins/discordHooks.ts index d84249b9e..f90309bcc 100644 --- a/api/src/plugins/discordHooks.ts +++ b/api/src/plugins/discordHooks.ts @@ -71,6 +71,7 @@ export async function handleTaskSolved( ); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any const discordMutationHook = (_build: Build) => (fieldContext: Context) => { const { scope: { isRootMutation }, @@ -99,9 +100,13 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { } const handleDiscordMutationAfter = async ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any input: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any context: any, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _resolveInfo: GraphQLResolveInfoWithMessages ) => { const guild = getDiscordGuild(); @@ -264,7 +269,12 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { const link = args.input.link; const ctfId = args.input.ctfId; - await syncDiscordPermissionsWithCtf(guild, ctfId, link).catch((err) => { + await syncDiscordPermissionsWithCtf( + guild, + ctfId, + link, + context.pgClient + ).catch((err) => { console.error("Failed to sync discord permissions.", err); }); } @@ -273,9 +283,13 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context) => { }; const handleDiscordMutationBefore = async ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any input: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any context: any, + // eslint-disable-next-line @typescript-eslint/no-unused-vars _resolveInfo: GraphQLResolveInfoWithMessages ) => { const guild = getDiscordGuild(); @@ -404,6 +418,7 @@ async function handeInvitation( await changeDiscordUserRoleForCTF(profileId, ctf, operation); } +// eslint-disable-next-line @typescript-eslint/no-explicit-any async function handleUpdateCtf(args: any, guild: Guild) { const ctf = await getCtfFromDatabase(args.input.id); if (ctf == null) return;