lots of shit

This commit is contained in:
Michael Zhang 2021-08-29 02:39:52 -05:00
parent 5c1f93db0c
commit dded1c1c2d
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
22 changed files with 258 additions and 135 deletions

View file

@ -1,2 +1,4 @@
material
===
Exercises are decoupled from the actual material and can appear wherever they want

View file

@ -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

View file

@ -1 +1,2 @@
/test.db
/dist

3
materialdb/db/index.ts Normal file
View file

@ -0,0 +1,3 @@
import { init, Page, Exercise, Grader } from "./page";
export { init, Page, Exercise, Grader };

View file

@ -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)

View file

@ -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<void> {
});
await exercise.save();
async function loadConceptsIntoDb(concept: string): Promise<void> {
};
async function loadGraderIntoDb([language, grader_cfg]: [string, GraderConfig]): Promise<void> {
let grader = new Grader({
page_slug: slug,

View file

@ -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",

View file

@ -3,6 +3,8 @@
"target": "es6",
"module": "commonjs",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"declaration": true,
"outDir": "dist"
}
}

View file

@ -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"

1
web/src/global.d.ts vendored
View file

@ -1 +1,2 @@
/// <reference types="@sveltejs/kit" />
/// <reference types="materialdb" />

View file

@ -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);
}

View file

@ -1,5 +1,5 @@
<script lang="ts">
import MultipleChoice from "$lib/activity/MultipleChoice.svelte";
import MultipleChoice from "$lib/exercise/MultipleChoice.svelte";
</script>
<MultipleChoice />

View file

@ -1,97 +0,0 @@
<script lang="ts">
export let question = {
description: `
what is 1 + 1?
`,
concepts: [ "addition" ],
choices: [
{ text: "1", correct: false },
{ text: "2", correct: true },
{ text: "3", correct: false },
],
};
// State related variables
let state = "ask";
let currentChoice: number;
let wasCorrect = false;
let choose = (index: number) => {
currentChoice = index;
};
let answer = async (sure: boolean) => {
state = "submitting";
let resp = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify({
}),
});
state = "answer";
wasCorrect = question.choices[currentChoice].correct;
};
</script>
<div class="quiz-box">
{#if state == "ask" }
<small>Q:</small>
<p>{ question.description }</p>
<ul class="choices">
{#each question.choices as choice, index }
<li>
<input type="radio" name="choice" on:change={() => choose(index)} />
{ choice.text }
</li>
{/each}
</ul>
<div class="answer-buttons">
<button>Skip</button>
<button on:click={() => answer(false)}>Not sure</button>
<button on:click={() => answer(true)}>I'm sure</button>
</div>
{:else if state == "submitting" }
submitting...
{:else}
<small>A:</small>
{#if wasCorrect }
<p>you Got it!</p>
{:else}
<p>you Failed</p>
{/if}
{/if}
</div>
<style lang="scss" scoped>
.quiz-box {
border: 1px solid gray;
border-radius: 8px;
padding: 8px;
.choices {
display: flex;
flex-direction: row;
justify-content: space-between;
li {
list-style: none;
flex-grow: 1;
margin: 12px;
button {
width: 100%;
}
}
}
.answer-buttons {
display: flex;
justify-content: flex-end;
button {
margin: 6px;
}
}
}
</style>

10
web/src/lib/auth.ts Normal file
View file

@ -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);
}

View file

@ -1,9 +0,0 @@
export function checkLogin(request) {
}
export function requireLogin() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}

1
web/src/lib/consts.ts Normal file
View file

@ -0,0 +1 @@

View file

@ -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<Sequelize> {
console.log("META", import.meta.env);
let sequelize = new Sequelize(`sqlite:${path}`, {
async function init(): Promise<Sequelize> {
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 };

16
web/src/lib/db/user.ts Normal file
View file

@ -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 {
}

View file

@ -0,0 +1,141 @@
<script lang="ts">
export let question = {
description: `
what is 1 + 1?
`,
concepts: [ "addition" ],
choices: [
{ text: "1", correct: false },
{ text: "2", correct: true },
{ text: "3", correct: false },
],
};
// State related variables
let state = "ask";
let currentChoice: number = -1;
let wasCorrect = false;
let choose = (index: number) => {
currentChoice = index;
};
let answer = async (sure: boolean) => {
state = "submitting";
let resp = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify({
}),
});
state = "answer";
wasCorrect = question.choices[currentChoice].correct;
};
let clearChoice = () => {
currentChoice = -1;
};
let goBack = () => {
state = "ask";
currentChoice = -1;
};
</script>
<div class="quiz-box">
{#if state == "ask" }
<small>Q:</small>
{:else}
<small>A:</small>
{/if}
<p>{ question.description }</p>
<ul class="choices">
{#each question.choices as choice, index }
<li>
<input type="radio"
name="choice"
class="box"
id={"choice" + index}
checked={currentChoice == index}
disabled={state != "ask"}
on:change={() => choose(index)} />
<label for={"choice" + index}>{ choice.text }</label>
</li>
{/each}
</ul>
{#if state == "ask" }
<div class="answer-buttons">
{#if currentChoice < 0}
<button>Skip</button>
{:else}
<button on:click={() => clearChoice()}>Clear</button>
{/if}
<button on:click={() => answer(false)} disabled={currentChoice < 0}>Not sure</button>
<button on:click={() => answer(true)} disabled={currentChoice < 0}>I'm sure</button>
</div>
{:else}
{#if wasCorrect }
<p>you Got it!</p>
{:else}
<p>you Failed</p>
{/if}
<div> <button on:click={() => goBack()}>Go back</button> </div>
{/if}
</div>
<style lang="scss" scoped>
.quiz-box {
border: 1px solid gray;
border-radius: 8px;
padding: 8px;
.choices {
display: flex;
flex-direction: row;
justify-content: space-between;
padding-left: 0;
li {
list-style: none;
flex-grow: 1;
margin: 12px;
width: 100%;
input.box {
display: none;
}
label {
border-radius: 8px;
cursor: pointer;
display: block;
width: 100%;
padding: 6px;
box-sizing: border-box;
text-align: center;
}
input.box:not(:checked) + label {
border: 2px solid #ccc;
}
input.box:checked + label {
border: 2px solid red;
background-color: lighten(red, 45%);
font-weight: bold;
}
}
}
.answer-buttons {
display: flex;
justify-content: flex-end;
button {
margin: 6px;
}
}
}
</style>

View file

@ -1 +0,0 @@
console.log("META", import.meta.env);

View file

@ -58,6 +58,7 @@
min-height: 100vh;
.content-wrap {
border-top: 10px solid #c00;
padding-bottom: $footer-height;
}
}

View file

@ -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 },
};
}