diff --git a/.github/ci-config.ts b/.github/ci-config.ts new file mode 100644 index 0000000..92efcc3 --- /dev/null +++ b/.github/ci-config.ts @@ -0,0 +1,15 @@ +import type { Config } from "drizzle-kit"; +import dotenv from "dotenv"; + +dotenv.config(); + +export default { + schema: "./db/schema.ts", + out: "./db/migrations", + dialect: "sqlite", + driver: "better-sqlite", + dbCredentials: { + url: process.env.DATABASE_URL, + }, + verbose: true, +} as Config; diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..dbe15a5 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,22 @@ +name: CI +'on': + - push +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: | + npm install + npm install dotenv-cli + - name: Run Prettier + run: | + npm run check + - name: Set up db + run: | + echo -e 'PORT=3000\nDATABASE_URL="./db/test.db"' > .env.test + npx dotenv -e .env.test -- drizzle-kit push --config=.github/ci-config.ts + - name: Run tests + run: | + npm run test \ No newline at end of file diff --git a/README.md b/README.md index a8b2bb1..c211bd4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ npm install npm install -g dotenv-cli ``` -5. Add a `.env` and `.env.test` file it your root with the following configurations: +5. Add a `.env` and `.env.test` file in your root with the following configurations: ```bash PORT=3000 diff --git a/app/challenges-platform/models/Challenge.ts b/app/challenges-platform/models/Challenge.ts index 10c1237..4c98011 100644 --- a/app/challenges-platform/models/Challenge.ts +++ b/app/challenges-platform/models/Challenge.ts @@ -8,6 +8,7 @@ export enum Evaluation { MANUAL, AUTOMATIC, } + export class Challenge { id: number; uuid: string; @@ -16,6 +17,7 @@ export class Challenge { format: Format; evaluation: Evaluation; points: number; + deleted: boolean; constructor({ id, @@ -25,6 +27,7 @@ export class Challenge { format, evaluation, points, + deleted, }: { id: number; uuid: string; @@ -33,6 +36,7 @@ export class Challenge { format: Format; evaluation: Evaluation; points: number; + deleted: boolean; }) { this.id = id; this.uuid = uuid; @@ -41,5 +45,6 @@ export class Challenge { this.format = format; this.evaluation = evaluation; this.points = points; + this.deleted = deleted; } } diff --git a/app/challenges-platform/models/Transformer.ts b/app/challenges-platform/models/Transformer.ts index 800533d..5635cc0 100644 --- a/app/challenges-platform/models/Transformer.ts +++ b/app/challenges-platform/models/Transformer.ts @@ -34,6 +34,7 @@ export class BaseTransformer extends Transformer { format: Format.TEXT, points: payload.points, evaluation: Evaluation.MANUAL, + deleted: payload.deleted, }); return challenge; } diff --git a/app/challenges-platform/services/challenges-service.ts b/app/challenges-platform/services/challenges-service.ts index 3899f22..d1ad752 100644 --- a/app/challenges-platform/services/challenges-service.ts +++ b/app/challenges-platform/services/challenges-service.ts @@ -93,8 +93,19 @@ export const update = async (): Promise> => { return Err(new Error("Not implemented")); }; -export const destroy = async (): Promise> => { - // TODO: should mark a challenge as deleted (soft delete) - // TODO: add a "deleted" boolean column to the challenges table - return Err(new Error("Not implemented")); +export const destroy = async ( + id: string, +): Promise> => { + try { + const result = await db + .update(challenges) + .set({ deleted: true }) + .where(eq(challenges.uuid, id)) + .returning(); + const transformer = challengesPlatform.findTransformer(result[0].type); + const challenge = transformer.newChallenge(result[0]); + return Ok(challenge); + } catch (e) { + return Err(new Error("Failed to mark challenge as deleted")); + } }; diff --git a/app/challenges-platform/services/submissions-service.ts b/app/challenges-platform/services/submissions-service.ts index beaf221..5c2b181 100644 --- a/app/challenges-platform/services/submissions-service.ts +++ b/app/challenges-platform/services/submissions-service.ts @@ -33,8 +33,15 @@ export const findByUuid = async ( if (!challengeResult.ok) { return Err(new Error("Failed to find challenge")); } +<<<<<<< AJP/reviews-findByUuid const participantResult = await ParticipantsService.findById(record.participantId); +======= + if (challengeResult.val.deleted === true) { + return Err(new Error("Challenge is deleted")); + } + const participantResult = await ParticipantsService.findByUuid(participantId); +>>>>>>> main if (!participantResult.ok) { return Err(new Error("Failed to find participant")); } diff --git a/app/event-management/models/FlagChallenge.ts b/app/event-management/models/FlagChallenge.ts index 5764880..dcc28e3 100644 --- a/app/event-management/models/FlagChallenge.ts +++ b/app/event-management/models/FlagChallenge.ts @@ -55,6 +55,7 @@ export class FlagChallenge extends Challenge { format, points, evaluation: Evaluation.AUTOMATIC, + deleted: false, }); this.flag = flag; } diff --git a/app/event-management/models/GithubIssueChallenge.ts b/app/event-management/models/GithubIssueChallenge.ts index c4edce4..ab4b90b 100644 --- a/app/event-management/models/GithubIssueChallenge.ts +++ b/app/event-management/models/GithubIssueChallenge.ts @@ -54,6 +54,7 @@ export class GithubIssueChallenge extends Challenge { format: Format.MARKDOWN, points, evaluation: Evaluation.MANUAL, + deleted: false, }); } } diff --git a/app/event-management/models/PhotoChallenge.ts b/app/event-management/models/PhotoChallenge.ts index 3fdbf31..c73406c 100644 --- a/app/event-management/models/PhotoChallenge.ts +++ b/app/event-management/models/PhotoChallenge.ts @@ -52,6 +52,7 @@ export class PhotoChallenge extends Challenge { format, points, evaluation: Evaluation.MANUAL, + deleted: false, }); } } diff --git a/db/schema.ts b/db/schema.ts index ceba0e9..edc9b7a 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -11,6 +11,7 @@ export const challenges = sqliteTable("challenges", { metadata: text("metadata", { mode: "json" }), createdAt: text("created_at").default(sql`CURRENT_TIMESTAMP`), updatedAt: text("updated_at").default(sql`CURRENT_TIMESTAMP`), + deleted: integer("deleted", { mode: "boolean" }).notNull().default(false), }); export const accessibleChallenges = sqliteTable("accessible_challenges", { diff --git a/test/challenges-platform/custom-transformer.ts b/test/challenges-platform/custom-transformer.ts index 6e9e7b8..32fe6fe 100644 --- a/test/challenges-platform/custom-transformer.ts +++ b/test/challenges-platform/custom-transformer.ts @@ -62,6 +62,7 @@ export class CustomChallenge extends Challenge { format, points, evaluation: Evaluation.MANUAL, + deleted: false, }); this.propString = propString; this.propNumber = propNumber; diff --git a/test/challenges-platform/services/challenges-service.test.ts b/test/challenges-platform/services/challenges-service.test.ts index 918c72e..0cfa62a 100644 --- a/test/challenges-platform/services/challenges-service.test.ts +++ b/test/challenges-platform/services/challenges-service.test.ts @@ -72,9 +72,12 @@ describe("ChallengesService", () => { describe("destroy", () => { it("throws an error", async () => { - const result = await ChallengesService.destroy(); + const challenge = await challengeFactory(); - expect(result.err).toBe(true); + const result = await ChallengesService.destroy(challenge.uuid); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val.deleted).toBe(true); }); }); diff --git a/test/challenges-platform/services/submissions-service.test.ts b/test/challenges-platform/services/submissions-service.test.ts index 2968085..7427d16 100644 --- a/test/challenges-platform/services/submissions-service.test.ts +++ b/test/challenges-platform/services/submissions-service.test.ts @@ -1,4 +1,7 @@ -import { SubmissionService } from "../../../app/challenges-platform"; +import { + ChallengesService, + SubmissionService, +} from "../../../app/challenges-platform"; import { challengeFactory } from "../factories/challenge-factory"; import { participantFactory } from "../factories/participant-factory"; @@ -60,6 +63,25 @@ describe("SubmissionService", () => { expect(result.val.toString()).toBe("Error: Failed to find challenge"); }); }); + describe("when challenge is deleted", () => { + it("returns an error", async () => { + const factory = await challengeFactory(); + // Delete the challenge + const challenge = await ChallengesService.destroy(factory.uuid); + if (!challenge.ok) + fail("Expected challenge to be Ok, something went really wrong"); + + const participant = await participantFactory(); + + const result = await SubmissionService.create( + challenge.val.uuid, + participant.uuid, + ); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Challenge is deleted"); + }); + }); describe("when participant does not exist", () => { it("returns an error", async () => { const challenge = await challengeFactory();