From fbe38546fda203e087dbc7ab4ce5834115b4157f Mon Sep 17 00:00:00 2001 From: AJaccP Date: Sat, 1 Jun 2024 01:47:33 +0530 Subject: [PATCH 1/3] added check to see if a participant can access a challenge --- .../services/submissions-service.ts | 15 ++++++++++++--- db/schema.ts | 9 +++++++++ .../services/submissions-service.test.ts | 4 ++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/app/challenges-platform/services/submissions-service.ts b/app/challenges-platform/services/submissions-service.ts index c2832ce..223aaf1 100644 --- a/app/challenges-platform/services/submissions-service.ts +++ b/app/challenges-platform/services/submissions-service.ts @@ -1,10 +1,11 @@ import { Ok, Err, Result } from "ts-results"; import { db } from "../../../db"; -import { submissions } from "../../../db/schema"; +import { submissions, accessibleChallenges, participants } from "../../../db/schema"; import { uuid } from "../../../app/common"; import { Submission } from "../models"; import { ChallengesService, ParticipantsService } from "../services"; import { challengesPlatform } from ".."; +import { eq } from "drizzle-orm"; export const create = async ( challengeId: string, @@ -21,8 +22,16 @@ export const create = async ( return Err(new Error("Failed to find participant")); } - // TODO: Validate that the participant is allowed to submit this challenge (available challenges) - // throw new Error("Participant is not allowed to submit this challenge") if not allowed (use participant-service) + const accessibleChallengesResult = await db + .select({pId: accessibleChallenges.participantId,cId: accessibleChallenges.challengeId}) + .from(accessibleChallenges) + .where(eq(accessibleChallenges.participantId, participantResult.val.id) && eq(accessibleChallenges.challengeId, challengeResult.val.id)) + .execute(); + + if (accessibleChallengesResult.length === 0) { + return Err(new Error("Participant is not allowed to submit this challenge" )); + } + // TODO: switch on submission.challenge.evaluation // if submission.challenge.evaluation is MANUAL, than save it to the database diff --git a/db/schema.ts b/db/schema.ts index 66282c7..4ada70f 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -13,6 +13,14 @@ export const challenges = sqliteTable("challenges", { updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), }); +export const accessibleChallenges = sqliteTable("accessible_challenges", { + id: integer("id").primaryKey(), + challengeId: integer("challenge_id").references(() => challenges.id), + participantId: integer("participant_id").references(() => participants.id), + createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), + updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), +}) + export const participants = sqliteTable("participants", { id: integer("id").primaryKey(), uuid: text("uuid").notNull(), @@ -40,3 +48,4 @@ export const reviews = sqliteTable("reviews", { createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), }); + diff --git a/test/challenges-platform/services/submissions-service.test.ts b/test/challenges-platform/services/submissions-service.test.ts index 25508b5..0788d86 100644 --- a/test/challenges-platform/services/submissions-service.test.ts +++ b/test/challenges-platform/services/submissions-service.test.ts @@ -13,7 +13,7 @@ describe("SubmissionService", () => { challenge.uuid, participant.uuid, ); - + if (!result.ok) fail("Expected result to be Ok"); expect(result.val.challenge.id).toBe(challenge.id); expect(result.val.participant.id).toBe(participant.id); @@ -46,4 +46,4 @@ describe("SubmissionService", () => { }); }); }); -}); +}); \ No newline at end of file From 0aaee4f9bf0b83667ef3005215148cec601bada7 Mon Sep 17 00:00:00 2001 From: Matthew M-B Date: Tue, 4 Jun 2024 18:06:57 -0400 Subject: [PATCH 2/3] Cleanup submissions validations --- .../services/accesible-challenges-service.ts | 24 ++++++ app/challenges-platform/services/index.ts | 1 + .../services/submissions-service.ts | 73 ++++++++++++------- db/schema.ts | 3 +- .../services/submissions-service.test.ts | 4 +- 5 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 app/challenges-platform/services/accesible-challenges-service.ts diff --git a/app/challenges-platform/services/accesible-challenges-service.ts b/app/challenges-platform/services/accesible-challenges-service.ts new file mode 100644 index 0000000..afeb5d7 --- /dev/null +++ b/app/challenges-platform/services/accesible-challenges-service.ts @@ -0,0 +1,24 @@ +import { accessibleChallenges } from "../../../db/schema"; +import { Ok, Result } from "ts-results"; +import { eq } from "drizzle-orm"; +import { db } from "../../../db"; +import { Challenge, Participant } from "../models"; + +export const count = async ( + challenge: Challenge, + participant: Participant, +): Promise> => { + const accessibleChallengesResult = await db + .select({ + pId: accessibleChallenges.participantId, + cId: accessibleChallenges.challengeId, + }) + .from(accessibleChallenges) + .where( + eq(accessibleChallenges.participantId, participant.id) && + eq(accessibleChallenges.challengeId, challenge.id), + ) + .execute(); + + return Ok(accessibleChallengesResult.length); +}; diff --git a/app/challenges-platform/services/index.ts b/app/challenges-platform/services/index.ts index 11b5ac8..ff88acb 100644 --- a/app/challenges-platform/services/index.ts +++ b/app/challenges-platform/services/index.ts @@ -1,3 +1,4 @@ +export * as AccesibleChallengesService from "./accesible-challenges-service"; export * as ChallengesService from "./challenges-service"; export * as ParticipantsService from "./participants-service"; export * as ReviewsService from "./reviews-service"; diff --git a/app/challenges-platform/services/submissions-service.ts b/app/challenges-platform/services/submissions-service.ts index 223aaf1..927e1eb 100644 --- a/app/challenges-platform/services/submissions-service.ts +++ b/app/challenges-platform/services/submissions-service.ts @@ -1,11 +1,14 @@ import { Ok, Err, Result } from "ts-results"; import { db } from "../../../db"; -import { submissions, accessibleChallenges, participants } from "../../../db/schema"; +import { submissions } from "../../../db/schema"; import { uuid } from "../../../app/common"; -import { Submission } from "../models"; -import { ChallengesService, ParticipantsService } from "../services"; +import { Challenge, Participant, Submission } from "../models"; +import { + AccesibleChallengesService, + ChallengesService, + ParticipantsService, +} from "../services"; import { challengesPlatform } from ".."; -import { eq } from "drizzle-orm"; export const create = async ( challengeId: string, @@ -13,25 +16,10 @@ export const create = async ( type: string = "base", metadata?: any, ): Promise> => { - const challengeResult = await ChallengesService.findByUuid(challengeId); - if (!challengeResult.ok) { - return Err(new Error("Failed to find challenge")); - } - const participantResult = await ParticipantsService.findByUuid(participantId); - if (!participantResult.ok) { - return Err(new Error("Failed to find participant")); - } - - const accessibleChallengesResult = await db - .select({pId: accessibleChallenges.participantId,cId: accessibleChallenges.challengeId}) - .from(accessibleChallenges) - .where(eq(accessibleChallenges.participantId, participantResult.val.id) && eq(accessibleChallenges.challengeId, challengeResult.val.id)) - .execute(); - - if (accessibleChallengesResult.length === 0) { - return Err(new Error("Participant is not allowed to submit this challenge" )); - } + const result = await beforeCreate(challengeId, participantId); + if (!result.ok) return result; + const [challenge, participant] = result.val; // TODO: switch on submission.challenge.evaluation // if submission.challenge.evaluation is MANUAL, than save it to the database @@ -52,18 +40,51 @@ export const create = async ( .insert(submissions) .values({ uuid: id.toString(), - challengeId: challengeResult.val.id, - participantId: participantResult.val.id, + challengeId: challenge.id, + participantId: participant.id, }) .returning(); const submission = transformer.newSubmission( result[0], - challengeResult.val, - participantResult.val, + challenge, + participant, ); return Ok(submission); } catch (e) { return Err(new Error("Failed to create submission")); } }; + +// private +const beforeCreate = async ( + challengeId: string, + participantId: string, +): Promise> => { + const challengeResult = await ChallengesService.findByUuid(challengeId); + if (!challengeResult.ok) { + return Err(new Error("Failed to find challenge")); + } + + const participantResult = await ParticipantsService.findByUuid(participantId); + if (!participantResult.ok) { + return Err(new Error("Failed to find participant")); + } + + const countResult = await AccesibleChallengesService.count( + challengeResult.val, + participantResult.val, + ); + if (!countResult.ok) { + return Err(new Error("Failed to count accessible challenges")); + } + + const count = countResult.val; + if (count === 0) { + return Err( + new Error("Participant is not allowed to submit this challenge"), + ); + } + + return Ok([challengeResult.val, participantResult.val]); +}; diff --git a/db/schema.ts b/db/schema.ts index 4ada70f..ceba0e9 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -19,7 +19,7 @@ export const accessibleChallenges = sqliteTable("accessible_challenges", { participantId: integer("participant_id").references(() => participants.id), createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), -}) +}); export const participants = sqliteTable("participants", { id: integer("id").primaryKey(), @@ -48,4 +48,3 @@ export const reviews = sqliteTable("reviews", { createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), }); - diff --git a/test/challenges-platform/services/submissions-service.test.ts b/test/challenges-platform/services/submissions-service.test.ts index 0788d86..25508b5 100644 --- a/test/challenges-platform/services/submissions-service.test.ts +++ b/test/challenges-platform/services/submissions-service.test.ts @@ -13,7 +13,7 @@ describe("SubmissionService", () => { challenge.uuid, participant.uuid, ); - + if (!result.ok) fail("Expected result to be Ok"); expect(result.val.challenge.id).toBe(challenge.id); expect(result.val.participant.id).toBe(participant.id); @@ -46,4 +46,4 @@ describe("SubmissionService", () => { }); }); }); -}); \ No newline at end of file +}); From c0c15f506b87ee107d54caae08393e9b028fabba Mon Sep 17 00:00:00 2001 From: AJaccP Date: Fri, 14 Jun 2024 03:46:29 +0530 Subject: [PATCH 3/3] Added accessible challenges tests --- app/challenges-platform/index.ts | 1 + .../factories/accessible-challenge-factory.ts | 32 +++++++++++++++++ .../accessible-challenges-service.test.ts | 36 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 test/challenges-platform/factories/accessible-challenge-factory.ts create mode 100644 test/challenges-platform/services/accessible-challenges-service.test.ts diff --git a/app/challenges-platform/index.ts b/app/challenges-platform/index.ts index ff4dd23..1bf1e39 100644 --- a/app/challenges-platform/index.ts +++ b/app/challenges-platform/index.ts @@ -3,6 +3,7 @@ export { ParticipantsService, ReviewsService, SubmissionService, + AccesibleChallengesService, } from "./services"; import { Transformer } from "./models"; import { transformers } from "../../config/challenges-platform/transformers"; diff --git a/test/challenges-platform/factories/accessible-challenge-factory.ts b/test/challenges-platform/factories/accessible-challenge-factory.ts new file mode 100644 index 0000000..3c7d146 --- /dev/null +++ b/test/challenges-platform/factories/accessible-challenge-factory.ts @@ -0,0 +1,32 @@ +import { challengeFactory } from "./challenge-factory"; +import { participantFactory } from "./participant-factory"; +import { + Challenge, + Participant, +} from "../../../app/challenges-platform/models"; +import { AccesibleChallengesService } from "../../../app/challenges-platform"; +import { db } from "../../../db"; +import { accessibleChallenges } from "../../../db/schema"; + +export const accessibleChallengeFactory = async ({ + challenge, + participant, + }: { + challenge?: Challenge; + participant?: Participant; + } = {}): Promise => { + const c = challenge || (await challengeFactory()); + const p = participant || (await participantFactory()); + + const insertResult = await db + .insert(accessibleChallenges) + .values({ + challengeId: c.id, + participantId: p.id, + }) + .returning(); + + const result = await AccesibleChallengesService.count(c, p); + if (!result.ok) fail("Expected result to be Ok"); + return result.val; + }; \ No newline at end of file diff --git a/test/challenges-platform/services/accessible-challenges-service.test.ts b/test/challenges-platform/services/accessible-challenges-service.test.ts new file mode 100644 index 0000000..4f819eb --- /dev/null +++ b/test/challenges-platform/services/accessible-challenges-service.test.ts @@ -0,0 +1,36 @@ +import { challengeFactory } from "../factories/challenge-factory"; +import { participantFactory } from "../factories/participant-factory"; +import { accessibleChallengeFactory } from "../factories/accessible-challenge-factory"; +import { AccesibleChallengesService } from "../../../app/challenges-platform"; + +describe("AccessibleChallengesService", () => { + describe("count", () => { + describe("when the challenge is not accessible", () => { + it("returns 0", async () => { + const challenge = await challengeFactory(); + const participant = await participantFactory(); + + const result = await AccesibleChallengesService.count(challenge, participant); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val).toBe(0); + }); + }); + describe("when the challenge is accessible", () => { + it("returns 1", async () => { + const challenge = await challengeFactory(); + const participant = await participantFactory(); + + const insert = await accessibleChallengeFactory({ + challenge, + participant + }); + + const result = await AccesibleChallengesService.count(challenge, participant); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val).toBe(1); + }); + }); + }) +}); \ No newline at end of file