From 87a0cb65973af33802285bbf825c60a00c1ce153 Mon Sep 17 00:00:00 2001 From: AJaccP Date: Tue, 2 Jul 2024 23:13:01 +0530 Subject: [PATCH] Add assign judge method to submissions --- app/challenges-platform/index.ts | 1 + app/challenges-platform/models/Judge.ts | 9 +++ app/challenges-platform/models/Submission.ts | 5 ++ app/challenges-platform/models/Transformer.ts | 2 + app/challenges-platform/models/index.ts | 1 + app/challenges-platform/services/index.ts | 1 + .../services/judges-service.ts | 26 +++++++ .../services/submissions-service.ts | 38 +++++++++++ app/event-management/models/FlagChallenge.ts | 6 +- .../models/GithubIssueChallenge.ts | 5 +- app/event-management/models/PhotoChallenge.ts | 5 +- db/schema.ts | 8 +++ .../challenges-platform/custom-transformer.ts | 6 +- .../factories/judge-factory.ts | 20 ++++++ .../services/judges-service.test.ts | 36 ++++++++++ .../services/submissions-service.test.ts | 67 +++++++++++++++++++ 16 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 app/challenges-platform/models/Judge.ts create mode 100644 app/challenges-platform/services/judges-service.ts create mode 100644 test/challenges-platform/factories/judge-factory.ts create mode 100644 test/challenges-platform/services/judges-service.test.ts diff --git a/app/challenges-platform/index.ts b/app/challenges-platform/index.ts index 042382b..c83dc7e 100644 --- a/app/challenges-platform/index.ts +++ b/app/challenges-platform/index.ts @@ -4,6 +4,7 @@ export { ReviewsService, SubmissionService, AccessibleChallengesService, + JudgesService, } from "./services"; import { Transformer } from "./models"; import { transformers } from "../../config/challenges-platform/transformers"; diff --git a/app/challenges-platform/models/Judge.ts b/app/challenges-platform/models/Judge.ts new file mode 100644 index 0000000..5222bda --- /dev/null +++ b/app/challenges-platform/models/Judge.ts @@ -0,0 +1,9 @@ +export class Judge { + id: number; + uuid: string; + + constructor({ id, uuid }: { id: number; uuid: string }) { + this.id = id; + this.uuid = uuid; + } +} diff --git a/app/challenges-platform/models/Submission.ts b/app/challenges-platform/models/Submission.ts index 2f70520..bfd10e8 100644 --- a/app/challenges-platform/models/Submission.ts +++ b/app/challenges-platform/models/Submission.ts @@ -1,26 +1,31 @@ import { Challenge } from "./Challenge"; import { Participant } from "./Participant"; +import { Judge } from "./Judge"; export class Submission { id: number; uuid: string; challenge: Challenge; participant: Participant; + assignee: Judge | null; constructor({ id, uuid, challenge, participant, + assignee, }: { id: number; uuid: string; challenge: Challenge; participant: Participant; + assignee: Judge | null; }) { this.id = id; this.uuid = uuid; this.challenge = challenge; this.participant = participant; + this.assignee = assignee; } } diff --git a/app/challenges-platform/models/Transformer.ts b/app/challenges-platform/models/Transformer.ts index 5635cc0..fce3ca4 100644 --- a/app/challenges-platform/models/Transformer.ts +++ b/app/challenges-platform/models/Transformer.ts @@ -1,6 +1,7 @@ import { Challenge, Evaluation, Format } from "./Challenge"; import { Participant } from "./Participant"; import { Submission } from "./Submission"; +import { Judge } from "./Judge"; export abstract class Transformer { public static newChallenge(payload: any): Challenge { @@ -53,6 +54,7 @@ export class BaseTransformer extends Transformer { uuid: payload.uuid, challenge: challenge, participant: participant, + assignee: null, }); return submission; } diff --git a/app/challenges-platform/models/index.ts b/app/challenges-platform/models/index.ts index f6b9bc5..d0735c7 100644 --- a/app/challenges-platform/models/index.ts +++ b/app/challenges-platform/models/index.ts @@ -3,3 +3,4 @@ export { Participant } from "./Participant"; export { Review, Status } from "./Review"; export { Submission } from "./Submission"; export { Transformer } from "./Transformer"; +export { Judge } from "./Judge"; diff --git a/app/challenges-platform/services/index.ts b/app/challenges-platform/services/index.ts index fe4871b..a6a1f53 100644 --- a/app/challenges-platform/services/index.ts +++ b/app/challenges-platform/services/index.ts @@ -3,3 +3,4 @@ export * as ChallengesService from "./challenges-service"; export * as ParticipantsService from "./participants-service"; export * as ReviewsService from "./reviews-service"; export * as SubmissionService from "./submissions-service"; +export * as JudgesService from "./judges-service"; diff --git a/app/challenges-platform/services/judges-service.ts b/app/challenges-platform/services/judges-service.ts new file mode 100644 index 0000000..a67f754 --- /dev/null +++ b/app/challenges-platform/services/judges-service.ts @@ -0,0 +1,26 @@ +import { eq } from "drizzle-orm"; +import { Ok, Err, Result } from "ts-results"; +import { db } from "../../../db"; +import { judges } from "../../../db/schema"; +import { uuid } from "../../../app/common"; +import { Judge } from "../models"; + +export const findByUuid = async (id: string): Promise> => { + if (!uuid.isValid(id)) { + return Err(new Error("Invalid UUID")); + } + + const result = await db.select().from(judges).where(eq(judges.uuid, id)); + + if (result.length === 0) { + return Err(new Error("Judge not found")); + } + + const record = result[0]; + const judge = new Judge({ + id: record.id, + uuid: record.uuid, + }); + + return Ok(judge); +}; diff --git a/app/challenges-platform/services/submissions-service.ts b/app/challenges-platform/services/submissions-service.ts index 6fbbf02..4043e85 100644 --- a/app/challenges-platform/services/submissions-service.ts +++ b/app/challenges-platform/services/submissions-service.ts @@ -8,6 +8,7 @@ import { AccessibleChallengesService, ChallengesService, ParticipantsService, + JudgesService, } from "../services"; import { challengesPlatform } from ".."; @@ -135,3 +136,40 @@ const beforeCreate = async ( return Ok([challengeResult.val, participantResult.val]); }; + +export const assign = async ( + submissionId: string, + judgeId: string, +): Promise> => { + const submissionResult = await findByUuid(submissionId); + if (!submissionResult.ok) { + return Err(new Error("Failed to find submission")); + } + + const judgeResult = await JudgesService.findByUuid(judgeId); + if (!judgeResult.ok) { + return Err(new Error("Failed to find judge")); + } + + const submission = submissionResult.val; + + const result = await db + .update(submissions) + .set({ assignee: judgeResult.val.id }) + .where(eq(submissions.id, submission.id)) + .returning(); + + if (result.length === 0) { + return Err(new Error("Failed to assign judge")); + } + + const updatedSubmission = new Submission({ + id: submission.id, + uuid: submission.uuid, + challenge: submission.challenge, + participant: submission.participant, + assignee: judgeResult.val, + }); + + return Ok(updatedSubmission); +}; diff --git a/app/event-management/models/FlagChallenge.ts b/app/event-management/models/FlagChallenge.ts index dcc28e3..a5a374f 100644 --- a/app/event-management/models/FlagChallenge.ts +++ b/app/event-management/models/FlagChallenge.ts @@ -5,6 +5,7 @@ import { Participant, Submission, Transformer, + Judge, } from "../../challenges-platform/models"; export class FlagChallengeSubmission extends Submission { @@ -16,14 +17,16 @@ export class FlagChallengeSubmission extends Submission { challenge, participant, flag, + assignee, }: { id: number; uuid: string; challenge: Challenge; participant: Participant; flag: string; + assignee: Judge | null; }) { - super({ id, uuid, challenge, participant }); + super({ id, uuid, challenge, participant, assignee }); this.flag = flag; } } @@ -90,6 +93,7 @@ export class FlagTransformer extends Transformer { challenge: challenge, participant: participant, flag: payload.flag, + assignee: null, }); return submission; } diff --git a/app/event-management/models/GithubIssueChallenge.ts b/app/event-management/models/GithubIssueChallenge.ts index ab4b90b..aa23fa1 100644 --- a/app/event-management/models/GithubIssueChallenge.ts +++ b/app/event-management/models/GithubIssueChallenge.ts @@ -5,6 +5,7 @@ import { Participant, Submission, Transformer, + Judge, } from "../../challenges-platform/models"; export class GithubIssueChallengeSubmission extends Submission { @@ -18,6 +19,7 @@ export class GithubIssueChallengeSubmission extends Submission { repositoryId, challenge, participant, + assignee, }: { id: number; uuid: string; @@ -25,8 +27,9 @@ export class GithubIssueChallengeSubmission extends Submission { repositoryId: string; challenge: Challenge; participant: Participant; + assignee: Judge | null; }) { - super({ id, uuid, challenge, participant }); + super({ id, uuid, challenge, participant, assignee }); this.issueId = issueId; this.repositoryId = repositoryId; } diff --git a/app/event-management/models/PhotoChallenge.ts b/app/event-management/models/PhotoChallenge.ts index c73406c..f541122 100644 --- a/app/event-management/models/PhotoChallenge.ts +++ b/app/event-management/models/PhotoChallenge.ts @@ -5,6 +5,7 @@ import { Participant, Submission, Transformer, + Judge, } from "../../challenges-platform/models"; export class PhotoChallengeSubmission extends Submission { @@ -16,14 +17,16 @@ export class PhotoChallengeSubmission extends Submission { photoUrl, challenge, participant, + assignee, }: { id: number; uuid: string; photoUrl: string; challenge: Challenge; participant: Participant; + assignee: Judge | null; }) { - super({ id, uuid, challenge, participant }); + super({ id, uuid, challenge, participant, assignee }); this.photoUrl = photoUrl; } } diff --git a/db/schema.ts b/db/schema.ts index edc9b7a..0c85243 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -36,6 +36,7 @@ export const submissions = sqliteTable("submissions", { metadata: text("metadata", { mode: "json" }), challengeId: integer("challenge_id").references(() => challenges.id), participantId: integer("participant_id").references(() => participants.id), + assignee: integer("assignee").references(() => judges.id), createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), }); @@ -49,3 +50,10 @@ export const reviews = sqliteTable("reviews", { createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), }); + +export const judges = sqliteTable("judges", { + id: integer("id").primaryKey(), + uuid: text("uuid").notNull(), + createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), + updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), +}); diff --git a/test/challenges-platform/custom-transformer.ts b/test/challenges-platform/custom-transformer.ts index 32fe6fe..1b843ae 100644 --- a/test/challenges-platform/custom-transformer.ts +++ b/test/challenges-platform/custom-transformer.ts @@ -5,6 +5,7 @@ import { Participant, Submission, Transformer, + Judge, } from "../../app/challenges-platform/models"; export class CustomSubmission extends Submission { @@ -18,6 +19,7 @@ export class CustomSubmission extends Submission { participant, propString, propNumber, + assignee, }: { id: number; uuid: string; @@ -25,8 +27,9 @@ export class CustomSubmission extends Submission { participant: Participant; propString: string; propNumber: number; + assignee: Judge | null; }) { - super({ id, uuid, challenge, participant }); + super({ id, uuid, challenge, participant, assignee }); this.propString = propString; this.propNumber = propNumber; } @@ -103,6 +106,7 @@ export class CustomTransformer extends Transformer { participant: participant, propString: payload.propString, propNumber: payload.propNumber, + assignee: null, }); return submission; } diff --git a/test/challenges-platform/factories/judge-factory.ts b/test/challenges-platform/factories/judge-factory.ts new file mode 100644 index 0000000..c264b76 --- /dev/null +++ b/test/challenges-platform/factories/judge-factory.ts @@ -0,0 +1,20 @@ +import { db } from "../../../db"; +import { judges } from "../../../db/schema"; +import { uuid } from "../../../app/common"; +import { Judge } from "../../../app/challenges-platform/models"; + +export const judgeFactory = async (): Promise => { + const insertResult = await db + .insert(judges) + .values({ + uuid: uuid.create(), + }) + .returning(); + + const judge = new Judge({ + id: insertResult[0].id, + uuid: insertResult[0].uuid, + }); + + return judge; +}; diff --git a/test/challenges-platform/services/judges-service.test.ts b/test/challenges-platform/services/judges-service.test.ts new file mode 100644 index 0000000..e0b05e6 --- /dev/null +++ b/test/challenges-platform/services/judges-service.test.ts @@ -0,0 +1,36 @@ +import { JudgesService } from "../../../app/challenges-platform"; +import { judgeFactory } from "../factories/judge-factory"; +import { uuid } from "../../../app/common"; + +describe("JudgesService", () => { + describe("findByUuid", () => { + describe("when the id is invalid", () => { + it("returns an error", async () => { + const result = await JudgesService.findByUuid("invalid-id"); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Invalid UUID"); + }); + }); + + describe("when there is no record", () => { + it("returns an error", async () => { + const testUuid = uuid.create(); + const result = await JudgesService.findByUuid(testUuid); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Judge not found"); + }); + }); + + describe("when there is an existing record", () => { + it("returns the judge", async () => { + const judge = await judgeFactory(); + const result = await JudgesService.findByUuid(judge.uuid); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val.uuid).toBe(judge.uuid); + }); + }); + }); +}); diff --git a/test/challenges-platform/services/submissions-service.test.ts b/test/challenges-platform/services/submissions-service.test.ts index 6c6132f..3b41584 100644 --- a/test/challenges-platform/services/submissions-service.test.ts +++ b/test/challenges-platform/services/submissions-service.test.ts @@ -1,11 +1,15 @@ import { ChallengesService, SubmissionService, + JudgesService, } from "../../../app/challenges-platform"; import { challengeFactory } from "../factories/challenge-factory"; import { participantFactory } from "../factories/participant-factory"; +import { judgeFactory } from "../factories/judge-factory"; import { accessibleChallengeFactory } from "../factories/accessible-challenge-factory"; +import { submissionFactory } from "../factories/submission-factory"; import { uuid } from "../../../app/common"; +import { Judge } from "../../../app/challenges-platform/models"; describe("SubmissionService", () => { describe("findByUuid", () => { @@ -119,5 +123,68 @@ describe("SubmissionService", () => { expect(result.val.toString()).toBe("Error: Failed to find participant"); }); }); + }); //write assign tests here and also judge service tests + + describe("assign", () => { + describe("when the submission does not exist", () => { + it("returns an error", async () => { + const result = await SubmissionService.assign( + "invalid-uuid", + "invalid-uuid", + ); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Failed to find submission"); + }); + }); + describe("when the judge does not exist", () => { + it("returns an error", async () => { + const challenge = await challengeFactory(); + const participant = await participantFactory(); + const insert = await accessibleChallengeFactory({ + challenge, + participant, + }); + + const submission = await SubmissionService.create( + challenge.uuid, + participant.uuid, + ); + if (!submission.ok) fail("Expected submission to be Ok"); + const result = await SubmissionService.assign( + submission.val.uuid, + "invalid-uuid", + ); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Failed to find judge"); + }); + }); + describe("when the submission and judge exist", () => { + it("assigns the judge to the submission", async () => { + const challenge = await challengeFactory(); + const participant = await participantFactory(); + const judge = await judgeFactory(); + + const insert = await accessibleChallengeFactory({ + challenge, + participant, + }); + + const submission = await SubmissionService.create( + challenge.uuid, + participant.uuid, + ); + if (!submission.ok) fail("Expected submission to be Ok"); + + const assign = await SubmissionService.assign( + submission.val.uuid, + judge.uuid, + ); + + if (!assign.ok) fail("Expected assign to be Ok"); + expect(assign.val.assignee?.id).toBe(judge.id); + }); + }); }); });