This commit is contained in:
Michael Zhang 2021-09-01 08:32:43 -05:00
parent b3917dc756
commit 8c213b0714
Signed by: michael
GPG Key ID: BDA47A31A3C8EE6B
16 changed files with 162 additions and 25 deletions

View File

@ -34,8 +34,10 @@
"dependencies": {
"@types/materialdb": "file:../materialdb",
"materialdb": "file:../materialdb",
"monaco-editor": "^0.27.0",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.6.5",
"sequelize-typescript": "^2.1.0"
"sequelize-typescript": "^2.1.0",
"ts-pattern": "^3.2.5"
}
}

View File

@ -4,5 +4,6 @@ import { dbPromise } from "$lib/db";
export async function handle({ request, resolve }) {
request.locals.db = await dbPromise;
request.locals.loginStatus = checkLogin(request);
console.log("DB", request.locals.db);
return resolve(request);
}

View File

@ -1,16 +1,16 @@
<script lang="ts">
import MultipleChoice from "$lib/exercise/MultipleChoice.svelte";
import { onMount } from "svelte";
import Exercise from "$lib/exercise/Exercise.svelte";
let state = "loading";
let exercise = null;
(async function() {
onMount(async () => {
let resp = await fetch("/api/recommendExercise");
let body = await resp.json();
exercise = body.exercise;
state = "finished";
})();
});
</script>
{#if state == "loading"}

View File

@ -1,7 +1,16 @@
import { User } from "$lib/db";
import { match, select } from "ts-pattern";
type _LoginStatus =
| { type: "loggedIn"; }
| { type: "notLoggedIn"; }
export class LoginStatus {
constructor(public isLoggedIn: boolean, public username?: string) {}
constructor(
public isLoggedIn: boolean,
public user_id?: number,
public username?: string,
) {}
}
export function checkLogin(req: Request): LoginStatus {

View File

@ -0,0 +1,23 @@
<script lang="ts">
import { onMount } from "svelte";
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
export let language = "ocaml";
let editorElement;
onMount(() => {
let editor = monaco.editor.create(editorElement, {
language,
});
editor.layout();
});
</script>
<div bind:this={editorElement} class="editor" />
<style lang="scss" scoped>
.editor {
width: 100%;
min-height: 400px;
}
</style>

View File

@ -0,0 +1,14 @@
import { DataType, Unique, Column, Table, Model } from "sequelize-typescript";
/// An instance of an exercise, created to be solved by a specific user.
@Table
export class ExerciseInstance extends Model {
@Column(DataType.STRING)
public user_id: string;
@Column(DataType.STRING)
public exercise_name: string;
@Column(DataType.JSON)
public instance_props: any;
}

View File

@ -1,5 +1,6 @@
import { Sequelize } from "sequelize-typescript";
import { User } from "./user";
import { ExerciseInstance } from "./exercise";
import { Page, Exercise, Grader } from "materialdb/db";
@ -15,7 +16,7 @@ async function loadMaterialDb() {
async function init(): Promise<Sequelize> {
let sequelize = new Sequelize(`sqlite:test.db`, {
models: [User],
models: [User, ExerciseInstance],
});
await sequelize.sync({ force: true });
return sequelize;
@ -24,4 +25,4 @@ async function init(): Promise<Sequelize> {
export let dbPromise = init();
export let materialDb = loadMaterialDb();
export { User };
export { User, ExerciseInstance };

View File

@ -1,14 +1,22 @@
<script lang="ts">
import MultipleChoice from "./MultipleChoice.svelte";
import MultipleChoice from "./MultipleChoice/Component.svelte";
import GradedProgram from "./GradedProgram/Component.svelte";
import { ExerciseStyle, ExerciseInfo } from "$lib/exercise";
export let exercise;
export let exercise: ExerciseInfo;
</script>
{JSON.stringify(exercise)}
{exercise.style}
<details open>
<summary>Exercise Spec</summary>
<pre>{JSON.stringify(exercise, null, 2)}</pre>
</details>
{#if !exercise}
loading
{:else if exercise.style == "multipleChoice"}
<MultipleChoice />
{:else if exercise.style == ExerciseStyle.MultipleChoice}
<MultipleChoice props={exercise.props} />
{:else if exercise.style == ExerciseStyle.GradedProgram}
<GradedProgram props={exercise.props} />
{:else}
Sorry, no support for <code>{exercise.style}</code> type of questions yet!
{/if}

View File

@ -0,0 +1,9 @@
<script lang="ts">
import Editor from "$lib/components/Editor.svelte";
export let props;
</script>
<code>{props}</code>
<Editor />

View File

@ -0,0 +1,9 @@
import { ExerciseStyle, IExercise } from "..";
import Component from "./Component.svelte";
let spec: IExercise = {
style: ExerciseStyle.GradedProgram,
component: Component,
};
export default spec;

View File

@ -1,4 +1,5 @@
<script lang="ts">
export let props;
export let question = {
description: `
@ -51,7 +52,7 @@
<p>{ question.description }</p>
<ul class="choices">
{#each question.choices as choice, index }
{#each props.choices as choice, index }
<li>
<input type="radio"
name="choice"

View File

@ -0,0 +1,7 @@
export class Props {
}
export class MaskedInfo {
}

View File

@ -0,0 +1,36 @@
// mask the full exercise object to only the part that the client needs to see
// in order to present it to the user (so the user can't just peek into network
// transactions to see what the correct answer is)
import type { Exercise } from "materialdb/db";
import { ExerciseInstance } from "$lib/db";
import type { LoginStatus } from "$lib/auth";
import { ExerciseStyle } from ".";
export class ExerciseInfo {
public style: string;
public props: any;
}
export async function createInstance(loginStatus: LoginStatus, exercise: Exercise): Promise<ExerciseInfo> {
let props;
switch (exercise.style) {
case ExerciseStyle.GradedProgram:
break;
case ExerciseStyle.MultipleChoice:
break;
}
let instance = new ExerciseInstance({
user_id: loginStatus.user_id,
exercise_name: exercise.name,
props,
});
instance.save();
let info = new ExerciseInfo();
info.style = exercise.style;
info.props = props;
console.log("info", info);
return info;
}

View File

@ -0,0 +1,20 @@
import { createInstance, ExerciseInfo } from "./createInstance";
import type { SvelteComponentDev } from "svelte";
export enum ExerciseStyle {
GradedProgram = "gradedProgram",
MultipleChoice = "multipleChoice",
}
export interface IExercise {
style: ExerciseStyle;
component: SvelteComponentDev;
}
export interface IExerciseProps {
}
export interface IExerciseMaskedInfo {
}
export { createInstance, ExerciseInfo };

View File

@ -1,9 +0,0 @@
// mask the full exercise object to only the part that the client needs to see
// in order to present it to the user (so the user can't just peek into network
// transactions to see what the correct answer is)
import type { Exercise } from "materialdb/db";
export async function maskExercise(exercise: Exercise): Promise<any> {
return {};
}

View File

@ -4,10 +4,13 @@
import { Exercise } from "materialdb/db";
import type { Sequelize } from "sequelize-typescript";
import type { LoginStatus } from "$lib/auth";
import { createInstance } from "$lib/exercise";
export async function get(req) {
let db: Sequelize = req.locals.db;
console.log("login Status:", req.locals.loginStatus);
let loginStatus: LoginStatus = req.locals.loginStatus;
console.log("login Status:", loginStatus);
let exercise = await Exercise.findOne({
where: {
@ -17,7 +20,10 @@ export async function get(req) {
});
console.log("picked", exercise);
let instance = await createInstance(loginStatus, exercise);
console.log("instance", instance);
return {
body: { exercise },
body: { exercise: instance },
};
}