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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:latest
image: postgres@sha256:596e4c843a9db32269a3757624d8a6a6f633e01895acb83fe0842497fd897eb7
env:
POSTGRES_PASSWORD: ctfnote
POSTGRES_USER: ctfnote
Expand Down
35 changes: 35 additions & 0 deletions api/migrations/56-assign-users-to-task.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- Migration: Assign users to tasks by managers (GraphQL integration)
-- This migration introduces GraphQL functions to allow managers to assign/unassign any team member to a task.

-- GraphQL mutation for assign_user_to_task
CREATE OR REPLACE FUNCTION ctfnote.assign_user_to_task(profile_id int, task_id int)
RETURNS ctfnote.work_on_task
AS $$
WITH inserted AS (
INSERT INTO ctfnote.work_on_task (task_id, profile_id, active)
VALUES (assign_user_to_task.task_id, assign_user_to_task.profile_id, TRUE)
ON CONFLICT (task_id, profile_id) DO UPDATE
SET active = TRUE
RETURNING *
)
SELECT * FROM inserted;
$$
LANGUAGE SQL;

GRANT EXECUTE ON FUNCTION ctfnote.assign_user_to_task(profile_id int, task_id int) TO user_manager;

-- GraphQL mutation for unassign_user_from_task
CREATE OR REPLACE FUNCTION ctfnote.unassign_user_from_task(profile_id int, task_id int)
RETURNS ctfnote.work_on_task
AS $$
WITH updated AS (
UPDATE ctfnote.work_on_task
SET active = FALSE
WHERE task_id = unassign_user_from_task.task_id
AND profile_id = unassign_user_from_task.profile_id
RETURNING *
)
SELECT * FROM updated;
$$ LANGUAGE SQL;

GRANT EXECUTE ON FUNCTION ctfnote.unassign_user_from_task(profile_id int, task_id int) TO user_manager;
66 changes: 43 additions & 23 deletions api/src/discord/agile/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,6 @@ async function handleUpdateTask(
}
}

async function handleStartWorkingOn(
guild: Guild,
taskId: bigint,
userId: bigint
) {
await moveChannel(guild, taskId, null, ChannelMovingEvent.START);
await sendStartWorkingOnMessage(guild, userId, taskId);
}

async function handleUpdateUserRole(
guild: Guild,
userId: bigint,
Expand Down Expand Up @@ -177,7 +168,9 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context<any>) => {
fieldContext.scope.fieldName !== "deleteCtf" &&
fieldContext.scope.fieldName !== "updateUserRole" &&
fieldContext.scope.fieldName !== "setDiscordEventLink" &&
fieldContext.scope.fieldName !== "registerWithToken"
fieldContext.scope.fieldName !== "registerWithToken" &&
fieldContext.scope.fieldName !== "assignUserToTask" &&
fieldContext.scope.fieldName !== "unassignUserFromTask"
) {
return null;
}
Expand Down Expand Up @@ -225,7 +218,7 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context<any>) => {
});
break;
case "startWorkingOn":
handleStartWorkingOn(
sendStartWorkingOnMessage(
guild,
args.input.taskId,
context.jwtClaims.user_id
Expand Down Expand Up @@ -285,6 +278,27 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context<any>) => {
console.error("Failed to register with token.", err);
});
break;
case "assignUserToTask":
sendStartWorkingOnMessage(
guild,
args.input.profileId,
args.input.taskId,
context.jwtClaims.user_id
).catch((err) => {
console.error("Failed to start working on task.", err);
});
break;
case "unassignUserFromTask":
sendStopWorkingOnMessage(
guild,
args.input.profileId,
args.input.taskId,
false,
context.jwtClaims.user_id
).catch((err) => {
console.error("Failed to stop working on task.", err);
});
break;
default:
break;
}
Expand Down Expand Up @@ -348,31 +362,37 @@ const discordMutationHook = (_build: Build) => (fieldContext: Context<any>) => {
export async function sendStartWorkingOnMessage(
guild: Guild,
userId: bigint,
task: Task | bigint
task: Task | bigint,
assignedBy: bigint | null = null
) {
await moveChannel(guild, task, null, ChannelMovingEvent.START);
return sendMessageToTask(
guild,
task,
`${await convertToUsernameFormat(userId)} is working on this task!`
);

let msg = `${await convertToUsernameFormat(userId)} is working on this task!`;
if (assignedBy != null) {
msg += ` (assigned by: ${await convertToUsernameFormat(assignedBy)})`;
}

return sendMessageToTask(guild, task, msg);
}

export async function sendStopWorkingOnMessage(
guild: Guild,
userId: bigint,
task: Task | bigint,
cancel = false
cancel = false,
assignedBy: bigint | null = null
) {
let text = "stopped";
if (cancel) {
text = "cancelled";
}
return sendMessageToTask(
guild,
task,
`${await convertToUsernameFormat(userId)} ${text} working on this task!`
);

let msg = `${await convertToUsernameFormat(userId)} ${text} working on this task!`;
if (assignedBy != null) {
msg += ` (assigned by: ${await convertToUsernameFormat(assignedBy)})`;
}

return sendMessageToTask(guild, task, msg);
}

export async function handleDeleteCtf(ctfId: string | bigint, guild: Guild) {
Expand Down
Loading
Loading