diff --git a/material/README.md b/material/README.md index d706da5..744aca4 100644 --- a/material/README.md +++ b/material/README.md @@ -1,2 +1,4 @@ material === + +Exercises are decoupled from the actual material and can appear wherever they want diff --git a/material/fp-function.yml b/material/fp-function.yml index af1376b..2d0e6d9 100644 --- a/material/fp-function.yml +++ b/material/fp-function.yml @@ -18,6 +18,9 @@ exercises: description: | Write a function called `doubleIt` that takes an integer and doubles it. + satisfiesConcept: + - fp-function + graders: ocaml: style: studentModule @@ -35,6 +38,9 @@ exercises: description: | Which of the following can be described as a _function_? + concepts: + - fp-function + graders: ocaml: style: multipleChoice diff --git a/materialdb/.gitignore b/materialdb/.gitignore index 85e3775..00eda3d 100644 --- a/materialdb/.gitignore +++ b/materialdb/.gitignore @@ -1 +1,2 @@ /test.db +/dist diff --git a/materialdb/db/index.ts b/materialdb/db/index.ts new file mode 100644 index 0000000..697e23b --- /dev/null +++ b/materialdb/db/index.ts @@ -0,0 +1,3 @@ +import { init, Page, Exercise, Grader } from "./page"; + +export { init, Page, Exercise, Grader }; diff --git a/materialdb/db/page.ts b/materialdb/db/page.ts index 1a1918e..a3d251a 100644 --- a/materialdb/db/page.ts +++ b/materialdb/db/page.ts @@ -3,41 +3,52 @@ import { PrimaryKey, ForeignKey, HasMany, Sequelize, Column, Table, Model, DataT @Table export class Page extends Model { @PrimaryKey - @Column + @Column(DataType.STRING) public slug: string; - @Column + @Column(DataType.STRING) public title: string; - @Column + @Column(DataType.STRING) public type: string; - @Column + @Column(DataType.STRING) public content: string; } @Table export class Exercise extends Model { @PrimaryKey - @Column + @Column(DataType.STRING) public name: string; @HasMany(() => Grader) public graders: Grader[]; } +@Table +export class ExerciseSatisfiesConcept extends Model { + @ForeignKey(() => Exercise) + @Column(DataType.STRING) + public exercise_name: string; + + @ForeignKey(() => Page) + @Column(DataType.STRING) + public concept_slug: string; +} + @Table export class Grader extends Model { @PrimaryKey @ForeignKey(() => Exercise) - @Column + @Column(DataType.STRING) public exercise_name: string; @PrimaryKey - @Column + @Column(DataType.STRING) public language: string; - @Column + @Column(DataType.STRING) public style: string; @Column(DataType.JSON) diff --git a/materialdb/index.ts b/materialdb/index.ts index ea5b708..933d896 100644 --- a/materialdb/index.ts +++ b/materialdb/index.ts @@ -4,7 +4,7 @@ import * as yaml from "js-yaml"; import { plainToClass } from "class-transformer"; import { validate } from "class-validator"; -import { init, Page, Exercise, Grader } from "./db/page"; +import { init, Page, Exercise, Grader } from "./db"; import { Page as PageConfig, Exercise as ExerciseConfig, Grader as GraderConfig } from "./page"; // TODO: configure this thru cmdline or something later @@ -45,6 +45,9 @@ async function loadPageIntoDb(name: string): Promise { }); await exercise.save(); + async function loadConceptsIntoDb(concept: string): Promise { + }; + async function loadGraderIntoDb([language, grader_cfg]: [string, GraderConfig]): Promise { let grader = new Grader({ page_slug: slug, diff --git a/materialdb/package.json b/materialdb/package.json index 435671b..6d3465e 100644 --- a/materialdb/package.json +++ b/materialdb/package.json @@ -1,7 +1,9 @@ { "name": "materialdb", + "types": "dist/index.d.ts", "scripts": { - "start": "ts-node index.ts" + "start": "ts-node index.ts", + "gen": "tsc --declaration" }, "devDependencies": { "@types/js-yaml": "^4.0.3", @@ -10,6 +12,7 @@ "ts-node": "^10.2.1" }, "dependencies": { + "typescript": "^4.4.2", "class-transformer": "^0.4.0", "class-validator": "^0.13.1", "js-yaml": "^4.1.0", diff --git a/materialdb/tsconfig.json b/materialdb/tsconfig.json index 6935746..2acff30 100644 --- a/materialdb/tsconfig.json +++ b/materialdb/tsconfig.json @@ -3,6 +3,8 @@ "target": "es6", "module": "commonjs", "emitDecoratorMetadata": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "declaration": true, + "outDir": "dist" } } diff --git a/web/package.json b/web/package.json index 64cc56f..ad99518 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,7 @@ "devDependencies": { "@sveltejs/kit": "next", "@types/node": "^16.7.5", + "@types/sequelize": "^4.28.10", "@types/validator": "^13.6.3", "@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/parser": "^4.19.0", @@ -31,6 +32,8 @@ }, "type": "module", "dependencies": { + "@types/materialdb": "file:../materialdb", + "materialdb": "file:../materialdb", "reflect-metadata": "^0.1.13", "sequelize": "^6.6.5", "sequelize-typescript": "^2.1.0" diff --git a/web/src/global.d.ts b/web/src/global.d.ts index 63908c6..6f7d10a 100644 --- a/web/src/global.d.ts +++ b/web/src/global.d.ts @@ -1 +1,2 @@ /// +/// diff --git a/web/src/hooks/index.ts b/web/src/hooks/index.ts index 2dec38e..0486573 100644 --- a/web/src/hooks/index.ts +++ b/web/src/hooks/index.ts @@ -1,8 +1,8 @@ import { checkLogin } from "$lib/auth"; -import { db } from "$lib/db"; +import { dbPromise } from "$lib/db"; export async function handle({ request, resolve }) { + request.locals.db = await dbPromise; request.locals.loginStatus = checkLogin(request); - request.locals.db = await db; return resolve(request); } diff --git a/web/src/lib/MasteryDemo.svelte b/web/src/lib/MasteryDemo.svelte index bcfe213..3fa5d65 100644 --- a/web/src/lib/MasteryDemo.svelte +++ b/web/src/lib/MasteryDemo.svelte @@ -1,5 +1,5 @@ diff --git a/web/src/lib/activity/MultipleChoice.svelte b/web/src/lib/activity/MultipleChoice.svelte deleted file mode 100644 index b51a1a1..0000000 --- a/web/src/lib/activity/MultipleChoice.svelte +++ /dev/null @@ -1,97 +0,0 @@ - - -
- {#if state == "ask" } - Q: -

{ question.description }

-
    - {#each question.choices as choice, index } -
  • - choose(index)} /> - { choice.text } -
  • - {/each} -
- -
- - - -
- {:else if state == "submitting" } - submitting... - {:else} - A: - {#if wasCorrect } -

you Got it!

- {:else} -

you Failed

- {/if} - {/if} -
- - diff --git a/web/src/lib/auth.ts b/web/src/lib/auth.ts new file mode 100644 index 0000000..5e34853 --- /dev/null +++ b/web/src/lib/auth.ts @@ -0,0 +1,10 @@ +import { User } from "$lib/db"; + +export class LoginStatus { + constructor(public isLoggedIn: boolean, public username?: string) {} +} + +export function checkLogin(req: Request): LoginStatus { + console.log("req", req); + return new LoginStatus(false); +} diff --git a/web/src/lib/auth/index.ts b/web/src/lib/auth/index.ts deleted file mode 100644 index d277ef7..0000000 --- a/web/src/lib/auth/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function checkLogin(request) { - -} - -export function requireLogin() { - return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { - console.log("second(): called"); - }; -} diff --git a/web/src/lib/consts.ts b/web/src/lib/consts.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/web/src/lib/consts.ts @@ -0,0 +1 @@ + diff --git a/web/src/lib/db/index.ts b/web/src/lib/db/index.ts index c4d5beb..2c5d4db 100644 --- a/web/src/lib/db/index.ts +++ b/web/src/lib/db/index.ts @@ -1,24 +1,27 @@ -import { Sequelize, DataType, Unique, Column, Table, Model } from "sequelize-typescript"; +import { Sequelize } from "sequelize-typescript"; +import { User } from "./user"; -@Table -export class User extends Model { - @Unique - @Column(DataType.STRING) - public email: string; +import { Page, Exercise, Grader } from "materialdb/db"; + +async function loadMaterialDb() { + // TODO: ensure this database is read-only? + // possibly could just chmod -w it at the start but that's kinda hacky + let sequelize = new Sequelize(`sqlite:../materialdb/test.db`, { + models: [Page, Exercise, Grader], + }); + await sequelize.sync(); + return sequelize; } -async function loadMaterialDb(path?: string) { -} - -async function init(path: string): Promise { - console.log("META", import.meta.env); - - let sequelize = new Sequelize(`sqlite:${path}`, { +async function init(): Promise { + let sequelize = new Sequelize(`sqlite:test.db`, { models: [User], }); await sequelize.sync({ force: true }); return sequelize; } -export let db = init("test.db"); +export let dbPromise = init(); export let materialDb = loadMaterialDb(); + +export { User }; diff --git a/web/src/lib/db/user.ts b/web/src/lib/db/user.ts new file mode 100644 index 0000000..23e8da4 --- /dev/null +++ b/web/src/lib/db/user.ts @@ -0,0 +1,16 @@ +import { DataType, Unique, Column, Table, Model } from "sequelize-typescript"; + +@Table +export class User extends Model { + @Column(DataType.STRING) + public email: string; + + @Unique + @Column(DataType.STRING) + public normalizedEmail: string; +} + +@Table +export class UserMastery extends Model { + +} diff --git a/web/src/lib/exercise/MultipleChoice.svelte b/web/src/lib/exercise/MultipleChoice.svelte new file mode 100644 index 0000000..e4d1c93 --- /dev/null +++ b/web/src/lib/exercise/MultipleChoice.svelte @@ -0,0 +1,141 @@ + + +
+ {#if state == "ask" } + Q: + {:else} + A: + {/if} + +

{ question.description }

+
    + {#each question.choices as choice, index } +
  • + choose(index)} /> + +
  • + {/each} +
+ + {#if state == "ask" } +
+ {#if currentChoice < 0} + + {:else} + + {/if} + + +
+ {:else} + {#if wasCorrect } +

you Got it!

+ {:else} +

you Failed

+ {/if} +
+ {/if} +
+ + diff --git a/web/src/lib/vars.ts b/web/src/lib/vars.ts deleted file mode 100644 index 5efb963..0000000 --- a/web/src/lib/vars.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("META", import.meta.env); diff --git a/web/src/routes/__layout.svelte b/web/src/routes/__layout.svelte index ea8a648..f23527d 100644 --- a/web/src/routes/__layout.svelte +++ b/web/src/routes/__layout.svelte @@ -58,6 +58,7 @@ min-height: 100vh; .content-wrap { + border-top: 10px solid #c00; padding-bottom: $footer-height; } } diff --git a/web/src/routes/api/recommendExercise.ts b/web/src/routes/api/recommendExercise.ts new file mode 100644 index 0000000..a1cf125 --- /dev/null +++ b/web/src/routes/api/recommendExercise.ts @@ -0,0 +1,23 @@ +// recommend a NEW activity (not a review) to do for the user based on their +// current mastery levels +// + +import { Exercise } from "materialdb/db"; +import type { Sequelize } from "sequelize-typescript"; + +export async function get(req) { + let db: Sequelize = req.locals.db; + console.log("login Status:", req.locals.loginStatus); + + let exercise = await Exercise.findOne({ + where: { + // TODO: join against ExerciseSatisfiesConcept + }, + order: db.random(), + }); + console.log("picked", exercise); + + return { + body: { exercise }, + }; +}