From 821942351e020ad8eab4ce8eb629ee2cd764ad39 Mon Sep 17 00:00:00 2001 From: xWafl <73sampleperson@gmail.com> Date: Wed, 15 Jul 2020 11:42:52 -0400 Subject: [PATCH] feat: tutorial requirements --- ...20200715083645_add_requirements_to_texts.ts | 7 +++++++ .../api/src/modules/texts/actions/addText.ts | 6 ++++-- packages/api/src/modules/texts/router.test.ts | 8 ++++---- packages/api/src/modules/texts/router.ts | 18 +++++++++++++++++- packages/api/src/modules/texts/types/Text.ts | 3 +++ .../tutorials/actions/compareRequirements.ts | 12 ++++++++++++ .../tutorials/actions/completeTutorial.ts | 13 +++++++++++-- .../api/src/modules/tutorials/router.test.ts | 12 +++++++----- packages/api/src/modules/tutorials/router.ts | 9 ++++++--- .../src/modules/tutorials/types/Requirement.ts | 4 ++++ 10 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 packages/api/db/migrations/20200715083645_add_requirements_to_texts.ts create mode 100644 packages/api/src/modules/tutorials/actions/compareRequirements.ts create mode 100644 packages/api/src/modules/tutorials/types/Requirement.ts diff --git a/packages/api/db/migrations/20200715083645_add_requirements_to_texts.ts b/packages/api/db/migrations/20200715083645_add_requirements_to_texts.ts new file mode 100644 index 0000000..1c47b9b --- /dev/null +++ b/packages/api/db/migrations/20200715083645_add_requirements_to_texts.ts @@ -0,0 +1,7 @@ +import Knex from "knex"; + +export const up = async (knex: Knex) => + knex.schema.table("texts", table => table.jsonb("requirements")); + +export const down = async (knex: Knex) => + knex.schema.table("texts", table => table.dropColumn("requirements")); diff --git a/packages/api/src/modules/texts/actions/addText.ts b/packages/api/src/modules/texts/actions/addText.ts index 8dc9e55..74bc8f3 100644 --- a/packages/api/src/modules/texts/actions/addText.ts +++ b/packages/api/src/modules/texts/actions/addText.ts @@ -7,7 +7,8 @@ export default async ( difficulty: number, author: number, ordered = true, - tutorial = false + tutorial = false, + requirements ) => { await knex("texts").insert({ title, @@ -15,6 +16,7 @@ export default async ( difficulty, author, ordered, - tutorial + tutorial, + requirements }); }; diff --git a/packages/api/src/modules/texts/router.test.ts b/packages/api/src/modules/texts/router.test.ts index 92134b4..466e50e 100644 --- a/packages/api/src/modules/texts/router.test.ts +++ b/packages/api/src/modules/texts/router.test.ts @@ -57,8 +57,8 @@ describe("Texts routes", async () => { .expect(200); expect(response.body).to.deep.equal([ - { ...newText, id: 1 }, - { ...newRandomText, id: 2 } + { ...newText, id: 1, requirements: null }, + { ...newRandomText, id: 2, requirements: null } ]); }); @@ -70,8 +70,8 @@ describe("Texts routes", async () => { .expect(200); expect([ - { ...newText, id: 1 }, - { ...newRandomText, id: 2 } + { ...newText, id: 1, requirements: null }, + { ...newRandomText, id: 2, requirements: null } ]).to.deep.include(response.body); }); }); diff --git a/packages/api/src/modules/texts/router.ts b/packages/api/src/modules/texts/router.ts index f351dcf..b980600 100644 --- a/packages/api/src/modules/texts/router.ts +++ b/packages/api/src/modules/texts/router.ts @@ -7,6 +7,7 @@ import { addTextBody } from "./schema/addTextBody"; import { validateSchema } from "../schema/middleware/validateSchema"; import deleteText from "./actions/deleteText"; import editText from "./actions/editText"; +import { HttpError } from "../../common/error/classes/httpError"; const router = new Router({ prefix: "/texts" }); @@ -16,8 +17,23 @@ router.post( validateSchema(addTextBody, "body"), async (ctx, next) => { const { title, text, difficulty, ordered, tutorial } = ctx.request.body; + const requirements = ctx.request.body.requirements; + if (tutorial && !requirements) { + throw new HttpError( + 400, + "You need to have requirements for a tutorial!" + ); + } const { user } = ctx.session!; - await addText(title, text, difficulty, user, ordered, tutorial); + await addText( + title, + text, + difficulty, + user, + ordered, + tutorial, + requirements + ); ctx.status = 201; ctx.body = { message: "Successfully added text" diff --git a/packages/api/src/modules/texts/types/Text.ts b/packages/api/src/modules/texts/types/Text.ts index 568f390..cd3242b 100644 --- a/packages/api/src/modules/texts/types/Text.ts +++ b/packages/api/src/modules/texts/types/Text.ts @@ -1,3 +1,5 @@ +type Requirement = "wpm" | "rawwpm" | "acc"; + export default interface Text { id: number; title: string; @@ -6,4 +8,5 @@ export default interface Text { tutorial: boolean; difficulty: number; author: number; + requirements?: Record; } diff --git a/packages/api/src/modules/tutorials/actions/compareRequirements.ts b/packages/api/src/modules/tutorials/actions/compareRequirements.ts new file mode 100644 index 0000000..fbae007 --- /dev/null +++ b/packages/api/src/modules/tutorials/actions/compareRequirements.ts @@ -0,0 +1,12 @@ +import Requirement from "../types/Requirement"; + +const typesafeKeys = Object.keys as (o: T) => Array; + +export default ( + base: Record, + head: Record +) => { + return typesafeKeys(base) + .map(l => [base[l], head[l]]) + .every(l => l[1] > l[0]); +}; diff --git a/packages/api/src/modules/tutorials/actions/completeTutorial.ts b/packages/api/src/modules/tutorials/actions/completeTutorial.ts index b2a335e..0bbf246 100644 --- a/packages/api/src/modules/tutorials/actions/completeTutorial.ts +++ b/packages/api/src/modules/tutorials/actions/completeTutorial.ts @@ -1,8 +1,14 @@ import { Tutorial } from "../types/Tutorial"; import knex from "../../../../db/knex"; import User from "../../users/types/User"; +import compareRequirements from "./compareRequirements"; +import Requirement from "../types/Requirement"; -export default async (tutorialid: number, userid: number) => { +export default async ( + tutorialid: number, + userid: number, + requirements: Record +) => { const tutorial = await knex("texts") .where({ id: tutorialid, @@ -14,7 +20,10 @@ export default async (tutorialid: number, userid: number) => { id: userid }) .first(); - if (!tutorial || !user) return false; + if (!tutorial || !user) return null; + if (!compareRequirements(tutorial.requirements!, requirements)) { + return false; + } await knex("users") .update({ tutorials: knex.raw("array_append(tutorials, ?)", [tutorialid]) diff --git a/packages/api/src/modules/tutorials/router.test.ts b/packages/api/src/modules/tutorials/router.test.ts index 2025133..869a9fc 100644 --- a/packages/api/src/modules/tutorials/router.test.ts +++ b/packages/api/src/modules/tutorials/router.test.ts @@ -17,10 +17,10 @@ describe("Tutorials routes", async () => { password: "MaoGay" }); - await addText("blah", "blah", 1, 1, false, true); + await addText("blah", "blah", 1, 1, false, true, { wpm: 100 }); const response = await agent .post(`/api/tutorials/completeTutorial/`) - .send({ id: 3 }) + .send({ id: 3, requirements: { wpm: 110 } }) .set("Accept", "application/json") .expect("Content-Type", /json/) .expect(200); @@ -30,15 +30,17 @@ describe("Tutorials routes", async () => { ); }); - it("Cannot complete a non-existent tutorial", async () => { + it("Cannot complete a tutorial without meeting requirements", async () => { const response = await agent .post(`/api/tutorials/completeTutorial/`) - .send({ id: 1 }) + .send({ id: 3, requirements: { wpm: 90 } }) .set("Accept", "application/json") .expect("Content-Type", /json/) .expect(400); - expect(response.body.message).to.equal("That tutorial does not exist!"); + expect(response.body.message).to.equal( + "You do not meet the requirements for this tutorial" + ); }); it("Gets tutorials completed for the user", async () => { diff --git a/packages/api/src/modules/tutorials/router.ts b/packages/api/src/modules/tutorials/router.ts index 56286a4..31c624d 100644 --- a/packages/api/src/modules/tutorials/router.ts +++ b/packages/api/src/modules/tutorials/router.ts @@ -8,11 +8,14 @@ import getAllTutorials from "./actions/getAllTutorials"; const router = new Router({ prefix: "/tutorials" }); router.post("/completeTutorial", requireAuthenticated(), async (ctx, next) => { - const { id } = ctx.request.body; + const { id, requirements } = ctx.request.body; const { user } = ctx.session!; - const success = await completeTutorial(id, user); + const success = await completeTutorial(id, user, requirements); if (!success) { - throw new HttpError(400, "That tutorial does not exist!"); + throw new HttpError( + 400, + "You do not meet the requirements for this tutorial" + ); } ctx.status = 200; ctx.body = { diff --git a/packages/api/src/modules/tutorials/types/Requirement.ts b/packages/api/src/modules/tutorials/types/Requirement.ts new file mode 100644 index 0000000..942b6c7 --- /dev/null +++ b/packages/api/src/modules/tutorials/types/Requirement.ts @@ -0,0 +1,4 @@ +type Requirement = "wpm" | "rawwpm" | "acc"; + +// eslint-disable-next-line no-undef +export default Requirement;