Skip to content
Snippets Groups Projects
Commit 6f87305a authored by Linus Keiser's avatar Linus Keiser :speech_balloon:
Browse files

feat(data): initial data schema and parsing logic

parent 52808078
Branches
No related tags found
1 merge request!310Feat/data
Showing with 850 additions and 1154 deletions
if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
fi
source_url "https://raw.githubusercontent.com/cachix/devenv/82c0147677e510b247d8b9165c54f73d32dfd899/direnvrc" "sha256-7u4iDd1nZpxL4tCzmPG0dQgC5V+/44Ba+tHkPob1v2k="
watch_file flake.nix
watch_file flake.lock
if ! use flake . --impure
then
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
fi
use devenv
.devenv/
.direnv/
# Devenv
.devenv*
devenv.local.nix
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml
test-results
node_modules
......@@ -23,3 +30,6 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# dev DB
/data
/nix/store/mwlwaai23dbx08q7lc83dlc3cyr0hmb3-pre-commit-config.json
\ No newline at end of file
/nix/store/i2pcrqk7n8y3iwwbl36ai66617aifwn8-pre-commit-config.json
\ No newline at end of file
......@@ -43,6 +43,25 @@ async function connectToDatabase() {
}
const commands: Command[] = [
{
name: "Check Status",
description: "Check if the database is reachable",
cliCommand: 'status',
async execute(db: Surreal) {
try {
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000));
await Promise.race([
db.ping(),
timeout
]);
process.exit(0);
} catch (error) {
console.error("Database not reachable", error);
process.exit(1);
}
}
},
{
name: 'Apply Migrations',
description: 'Apply all migration files from the db/migrations directory',
......@@ -335,5 +354,8 @@ async function main() {
}
if (import.meta.main) {
main().catch(console.error);
main().catch((error) => {
console.error(error);
Deno.exit(1);
});
}
......@@ -35,5 +35,6 @@ DEFINE ACCESS user ON DATABASE TYPE RECORD
RETURN $auth;
}
DURATION FOR TOKEN 1h, FOR SESSION 12h
-- TODO reduce token lifetime and implement a refresh mechanism
DURATION FOR TOKEN 2h, FOR SESSION 12h
;
-- exam table
DEFINE TABLE exam SCHEMAFULL
PERMISSIONS
FOR select, update, delete WHERE id = $auth.id OR $auth.roles CONTAINS "admin"
FOR create WHERE $auth.roles CONTAINS "admin";
DEFINE FIELD name ON exam TYPE string;
DEFINE FIELD email ON exam TYPE string ASSERT string::is::email($value);
DEFINE FIELD password_hash ON exam TYPE option<string> DEFAULT NONE;
DEFINE FIELD enabled ON exam TYPE bool;
DEFINE FIELD roles ON exam TYPE set<"admin" | "reviewer" | "tutor" | "student"> DEFAULT ["tutor"];
DEFINE FIELD activation_token ON exam TYPE option<string> DEFAULT NONE;
DEFINE FIELD activation_expires ON exam TYPE option<datetime> DEFAULT NONE;
DEFINE INDEX email ON exam FIELDS email UNIQUE;
-- ------------------------------
-- OPTION
-- ------------------------------
-- OPTION IMPORT;
-- ------------------------------
-- TABLE: account_of
-- ------------------------------
DEFINE TABLE account_of TYPE RELATION IN user OUT student | tutor | reviewer SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD in ON account_of TYPE record<user> PERMISSIONS FULL;
DEFINE FIELD out ON account_of TYPE record<student | tutor | reviewer> PERMISSIONS FULL;
DEFINE INDEX many_to_one_user ON account_of FIELDS out UNIQUE;
-- ------------------------------
-- TABLE: assignment
-- ------------------------------
DEFINE TABLE assignment TYPE NORMAL SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD description ON assignment TYPE string PERMISSIONS FULL;
DEFINE FIELD full_score ON assignment TYPE float ASSERT $value >= 0 PERMISSIONS FULL;
DEFINE FIELD module ON assignment TYPE record<module> READONLY PERMISSIONS FULL;
DEFINE FIELD name ON assignment TYPE string READONLY PERMISSIONS FULL;
DEFINE FIELD programming_language ON assignment TYPE string PERMISSIONS FULL;
DEFINE FIELD solution ON assignment TYPE string PERMISSIONS FULL;
-- ------------------------------
-- TABLE: evaluation
-- ------------------------------
DEFINE TABLE evaluation TYPE RELATION IN tutor OUT submission SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD in ON evaluation TYPE record<tutor> PERMISSIONS FULL;
DEFINE FIELD out ON evaluation TYPE record<submission> PERMISSIONS FULL;
DEFINE FIELD score ON evaluation TYPE float PERMISSIONS FULL;
-- ------------------------------
-- TABLE: exercise_group
-- ------------------------------
DEFINE TABLE exercise_group TYPE NORMAL SCHEMALESS PERMISSIONS NONE;
-- ------------------------------
-- TABLE: member_of
-- ------------------------------
DEFINE TABLE member_of TYPE RELATION IN student OUT exercise_group SCHEMAFULL PERMISSIONS NONE;
DEFINE FIELD in ON member_of TYPE record<student> PERMISSIONS FULL;
DEFINE FIELD out ON member_of TYPE record<exercise_group> PERMISSIONS FULL;
DEFINE INDEX unique_relations ON member_of FIELDS in, out UNIQUE;
-- ------------------------------
-- TABLE: module
-- ------------------------------
DEFINE TABLE module TYPE NORMAL SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD pass_score ON module TYPE float ASSERT $value >= 0f PERMISSIONS FULL;
DEFINE FIELD reference ON module TYPE string READONLY PERMISSIONS FULL;
DEFINE FIELD total_score ON module TYPE float ASSERT $value >= 0f PERMISSIONS FULL;
DEFINE FIELD ungraded ON module TYPE bool DEFAULT false PERMISSIONS FULL;
DEFINE INDEX unique_reference ON module FIELDS reference UNIQUE;
-- ------------------------------
-- TABLE: review
-- ------------------------------
DEFINE TABLE review TYPE RELATION IN reviewer OUT evaluation SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD approved ON review TYPE bool PERMISSIONS FULL;
DEFINE FIELD in ON review TYPE record<reviewer> PERMISSIONS FULL;
DEFINE FIELD out ON review TYPE record<evaluation> PERMISSIONS FULL;
-- ------------------------------
-- TABLE: reviewer
-- ------------------------------
DEFINE TABLE reviewer TYPE NORMAL SCHEMALESS PERMISSIONS NONE;
-- ------------------------------
-- TABLE: student
-- ------------------------------
DEFINE TABLE student TYPE NORMAL SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD alias ON student TYPE string PERMISSIONS FULL;
-- ------------------------------
-- TABLE: submission
-- ------------------------------
DEFINE TABLE submission TYPE RELATION IN student | exercise_group OUT assignment SCHEMALESS PERMISSIONS NONE;
DEFINE FIELD code ON submission TYPE string PERMISSIONS FULL;
DEFINE FIELD created_at ON submission TYPE datetime DEFAULT time::now() PERMISSIONS FULL;
DEFINE FIELD in ON submission TYPE record<student | exercise_group> PERMISSIONS FULL;
DEFINE FIELD out ON submission TYPE record<assignment> PERMISSIONS FULL;
DEFINE FIELD source_code ON submission TYPE option<string> PERMISSIONS FULL;
DEFINE FIELD updated_at ON submission TYPE datetime VALUE time::now() PERMISSIONS FULL;
-- ------------------------------
-- TABLE: tutor
-- ------------------------------
DEFINE TABLE tutor TYPE NORMAL SCHEMALESS PERMISSIONS NONE;
import { RecordId, Surreal, type Token } from 'surrealdb';
const DB_URL = 'http://localhost:8000';
const NAMESPACE = 'grady';
const DATABASE = 'grady';
let db: Surreal | undefined;
export async function initDb(): Promise<Surreal> {
if (db) return db;
db = new Surreal();
try {
await db.connect(DB_URL);
await db.use({ namespace: NAMESPACE, database: DATABASE });
console.log('yup');
return db;
} catch (err) {
console.error('Failed to connect to SurrealDB:', err);
throw err;
}
}
export async function closeDb(): Promise<void> {
if (!db) return;
await db.close();
db = undefined;
}
export function getDb(): Surreal | undefined {
return db;
}
// TODO return user without password
export async function signUp(email: string, password: string): Promise<Token> {
const db = await initDb();
return db.signup({
namespace: NAMESPACE,
database: DATABASE,
scope: 'user',
email,
pass: password
});
}
export async function signIn(email: string, password: string): Promise<Token> {
const db = await initDb();
console.debug(db);
return db.signin({
namespace: NAMESPACE,
database: DATABASE,
access: 'user',
variables: {
email,
pass: password
}
});
}
export async function signOut(): Promise<boolean> {
const db = await initDb();
return await db.invalidate();
}
export async function getCurrentUser(): Promise<any | undefined> {
const db = await initDb();
return db.info<any>();
}
export async function createRole(name: string): Promise<void> {
const db = await initDb();
await db.query('fn::create_role', { name });
}
export async function assignRole(userId: string, roleName: string): Promise<void> {
const db = await initDb();
await db.query('fn::assign_role', { user_id: userId, role_name: roleName });
}
export async function createUserWithRole(
email: string,
password: string,
roleNames: string[]
): Promise<RecordId<'user'>> {
const db = await initDb();
const result = await db.query<[RecordId<'user'>]>('fn::create_user_with_role', {
email,
pass: password,
roles: roleNames
});
return result[0];
}
export async function getUserRoles(userId: string): Promise<string[]> {
const db = await initDb();
const result = await db.query<[{ roles: { name: string }[] }]>(
'SELECT roles.* FROM user WHERE id = $userId',
{ userId }
);
return result[0].roles.map((role) => role.name);
}
export async function hasRole(userId: string, roleName: string): Promise<boolean> {
const roles = await getUserRoles(userId);
return roles.includes(roleName);
}
db = await initDb();
console.log(db);
const token = await db?.signup({
namespace: 'grady',
database: 'grady',
access: 'user',
variables: {
email: 'john@gmail.com',
password: 'someGoodPassword!'
}
});
console.log(token);
File moved
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1731501663,
"owner": "cachix",
"repo": "devenv",
"rev": "b48b0d8018e0dc8c14d9dca47cd6e55add94a603",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1731531548,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "24f0d4acd634792badd6470134c387a3b039dace",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1731386116,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "689fed12a013f56d4c4d3f612489634267d86529",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1731531548,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "24f0d4acd634792badd6470134c387a3b039dace",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1731363552,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "cd1af27aa85026ac759d5d3fccf650abe7e1bbf0",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}
{ pkgs, lib, config, inputs, ... }:
let
pkgs-unstable = import inputs.nixpkgs-unstable { system = pkgs.stdenv.system; };
editor-deps = with pkgs-unstable; [
vscode-langservers-extracted
nodePackages.typescript-language-server
svelte-language-server
tailwindcss-language-server
];
# dev db config
surreal = {
port = toString 8000;
host = "localhost";
user = "root";
pass = "root";
dataDir = "data/surreal";
dbPath = "dev.db";
};
in
{
# https://devenv.sh/basics/
env = {
# SURREAL_NS = "grady";
# SURREAL_DB = "dev";
# SURREAL_USER = surreal.user;
# SURREAL_PASS = surreal.pass;
# SURREAL_HOST = surreal.host;
# SURREAL_PORT = surreal.port;
# SURREAL_DATA_DIR = surreal.dataDir;
# SURREAL_DATA_DB = surreal.dbPath;
};
dotenv.enable = true;
difftastic.enable = true;
# https://devenv.sh/packages/
packages = [ pkgs.git ] ++ editor-deps;
# https://devenv.sh/languages/
# languages.rust.enable = true;
languages = {
javascript = {
enable = true;
npm.enable = true;
};
typescript.enable = true;
deno.enable = true;
};
# https://devenv.sh/processes/
process.manager.implementation = "process-compose";
processes = {
surrealdb = {
exec = ''
mkdir -p ${surreal.dataDir}
docker run --rm --pull always \
-p ${surreal.port}:8000 \
--user $(id -u) \
-v $(pwd)/${surreal.dataDir}:/data \
surrealdb/surrealdb:latest start \
--user ${surreal.user}\
--pass ${surreal.pass}\
--log debug \
rocksdb:/data/${surreal.dbPath}
'';
process-compose = {
readiness_probe = {
exec = {
command = "./db/manage.ts status";
};
initial_delay_seconds = 5;
period_seconds = 5;
timeout_seconds = 5;
success_threshold = 1;
failure_threshold = 3;
};
};
};
grady = {
exec = ''
npm install
npm run dev
'';
process-compose = {
depends_on.surrealdb.condition = "process_healthy";
};
};
};
# https://devenv.sh/services/
# services.postgres.enable = true;
# https://devenv.sh/scripts/
# enterShell = ''
# git --version
# '';
# https://devenv.sh/tasks/
tasks = {
"app:dev" = {
exec = "npm run dev";
# status = "./db/manage.ts status";
};
"db:migrate" = {
exec = "./db/manage.ts migrate";
status = "./db/manage.ts status";
};
"db:info" = {
exec = "./db/manage.ts info";
status = "./db/manage.ts status";
};
"db:query" = {
exec = ''
if [ -z "$QUERY" ]; then
./db/manage.ts query
else
./db/manage.ts query --query "$QUERY"
fi
'';
status = "./db/manage.ts status";
};
};
# https://devenv.sh/tests/
enterTest = ''
echo "Running tests"
git --version | grep --color=auto "${pkgs.git.version}"
'';
# https://devenv.sh/pre-commit-hooks/
# pre-commit.hooks.shellcheck.enable = true;
pre-commit.hooks = {
eslint.enable = true;
prettier.enable = true;
prettier.settings = {
configPath = ".prettierrc";
};
};
# See full reference at https://devenv.sh/reference/options/
}
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
# url: github:cachix/devenv-nixpkgs/rolling
url: github:NixOS/nixpkgs/nixpkgs-unstable
nixpkgs-unstable:
url: github:NixOS/nixpkgs/nixpkgs-unstable
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend
{
"nodes": {
"cachix": {
"inputs": {
"devenv": "devenv_2",
"flake-compat": ["devenv", "flake-compat"],
"git-hooks": ["devenv", "pre-commit-hooks"],
"nixpkgs": ["devenv", "nixpkgs"]
},
"locked": {
"lastModified": 1726520618,
"narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
"owner": "cachix",
"repo": "cachix",
"rev": "695525f9086542dfb09fde0871dbf4174abbf634",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"cachix_2": {
"inputs": {
"devenv": "devenv_3",
"flake-compat": ["devenv", "cachix", "devenv", "flake-compat"],
"nixpkgs": ["devenv", "cachix", "devenv", "nixpkgs"],
"pre-commit-hooks": ["devenv", "cachix", "devenv", "pre-commit-hooks"]
},
"locked": {
"lastModified": 1712055811,
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
"owner": "cachix",
"repo": "cachix",
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat_2",
"nix": "nix_3",
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
"lastModified": 1730213537,
"narHash": "sha256-bWoeNdFISbGK8M0Xw4edmManGCkJ1oNqbfNY0Hlv9Vc=",
"owner": "cachix",
"repo": "devenv",
"rev": "5c046eeafd13f7a2b9fc733f70ea17571b24410f",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv-root": {
"flake": false,
"locked": {
"narHash": "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY=",
"type": "file",
"url": "file:///dev/null"
},
"original": {
"type": "file",
"url": "file:///dev/null"
}
},
"devenv_2": {
"inputs": {
"cachix": "cachix_2",
"flake-compat": ["devenv", "cachix", "flake-compat"],
"nix": "nix_2",
"nixpkgs": ["devenv", "cachix", "nixpkgs"],
"pre-commit-hooks": ["devenv", "cachix", "git-hooks"]
},
"locked": {
"lastModified": 1723156315,
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
"owner": "cachix",
"repo": "devenv",
"rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv_3": {
"inputs": {
"flake-compat": ["devenv", "cachix", "devenv", "cachix", "flake-compat"],
"nix": "nix",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix",
"pre-commit-hooks": ["devenv", "cachix", "devenv", "cachix", "pre-commit-hooks"]
},
"locked": {
"lastModified": 1708704632,
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
"owner": "cachix",
"repo": "devenv",
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "python-rewrite",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": ["devenv", "nix", "nixpkgs"]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1727826117,
"narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": ["devenv", "pre-commit-hooks", "nixpkgs"]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"mk-shell-bin": {
"locked": {
"lastModified": 1677004959,
"narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=",
"owner": "rrbutani",
"repo": "nix-mk-shell-bin",
"rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887",
"type": "github"
},
"original": {
"owner": "rrbutani",
"repo": "nix-mk-shell-bin",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": ["devenv", "cachix", "devenv", "cachix", "devenv", "nixpkgs"],
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": ["devenv", "cachix", "devenv", "cachix", "devenv", "poetry2nix", "nixpkgs"]
},
"locked": {
"lastModified": 1688870561,
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nix2container": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixpkgs": ["nixpkgs"]
},
"locked": {
"lastModified": 1729339656,
"narHash": "sha256-smV7HQ/OqZeRguQxNjsb3uQDwm0p6zKDbSDbPCav/oY=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "cc96df7c3747c61c584d757cfc083922b4f4b33e",
"type": "github"
},
"original": {
"owner": "nlewo",
"repo": "nix2container",
"type": "github"
}
},
"nix_2": {
"inputs": {
"flake-compat": ["devenv", "cachix", "devenv", "flake-compat"],
"nixpkgs": ["devenv", "cachix", "devenv", "nixpkgs"],
"nixpkgs-regression": "nixpkgs-regression_2"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nix_3": {
"inputs": {
"flake-compat": ["devenv", "flake-compat"],
"flake-parts": "flake-parts",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_2",
"nixpkgs-23-11": "nixpkgs-23-11",
"nixpkgs-regression": "nixpkgs-regression_3",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1727438425,
"narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
"owner": "domenkozar",
"repo": "nix",
"rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1692808169,
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-23-11": {
"locked": {
"lastModified": 1717159533,
"narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1727825735,
"narHash": "sha256-0xHYkMkeLVQAMa7gvkddbPqpxph+hDzdu1XdGPJR+Os=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/fb192fec7cc7a4c26d51779e9bab07ce6fa5597a.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/fb192fec7cc7a4c26d51779e9bab07ce6fa5597a.tar.gz"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-regression_2": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-regression_3": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1720386169,
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1716977621,
"narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1729880355,
"narHash": "sha256-RP+OQ6koQQLX5nw0NmcDrzvGL8HDLnyXt/jHhL1jwjM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18536bf04cd71abd345f9579158841376fdd0c5a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": ["devenv", "cachix", "devenv", "cachix", "devenv", "nixpkgs"]
},
"locked": {
"lastModified": 1692876271,
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": ["devenv", "nix"],
"flake-utils": "flake-utils_2",
"gitignore": ["devenv", "nix"],
"nixpkgs": ["devenv", "nix", "nixpkgs"],
"nixpkgs-stable": ["devenv", "nix", "nixpkgs"]
},
"locked": {
"lastModified": 1712897695,
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_2": {
"inputs": {
"flake-compat": ["devenv", "flake-compat"],
"gitignore": "gitignore",
"nixpkgs": ["devenv", "nixpkgs"],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1726745158,
"narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"devenv-root": "devenv-root",
"flake-parts": "flake-parts_2",
"mk-shell-bin": "mk-shell-bin",
"nix2container": "nix2container",
"nixpkgs": "nixpkgs_4"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
{
description = "Grady devenv";
inputs = {
devenv-root = {
url = "file+file:///dev/null";
flake = false;
};
flake-parts.url = "github:hercules-ci/flake-parts";
# nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
devenv.url = "github:cachix/devenv";
nix2container.url = "github:nlewo/nix2container";
nix2container.inputs.nixpkgs.follows = "nixpkgs";
mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin";
};
nixConfig = {
extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
extra-substituters = "https://devenv.cachix.org";
};
outputs = inputs@{ flake-parts, devenv-root, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.devenv.flakeModule
];
systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
perSystem = { config, self', inputs', pkgs, system, ... }: {
# Per-system attributes can be defined here. The self' and inputs'
# module parameters provide easy access to attributes of the same
# system.
# Equivalent to inputs'.nixpkgs.legacyPackages.hello;
packages.default = pkgs.hello;
devenv.shells.default =
let
surrealImage = "surrealdb/surrealdb:latest";
in
{
devenv.root =
let
devenvRootFileContent = builtins.readFile devenv-root.outPath;
in
pkgs.lib.mkIf (devenvRootFileContent != "") devenvRootFileContent;
name = "Grady";
imports = [
# This is just like the imports in devenv.nix.
# See https://devenv.sh/guides/using-with-flake-parts/#import-a-devenv-module
# ./devenv-foo.nix
];
dotenv.enable = true;
# https://devenv.sh/reference/options/
packages = [ config.packages.default ] ++ (with pkgs; [
nodePackages.typescript-language-server
svelte-language-server
tailwindcss-language-server
vscode-langservers-extracted
]);
scripts = {
# dev.exec = "bun run dev &";
db_dev.exec = ''
docker run -d --rm --pull always --name surrealdb -p 8000:8000 ${surrealImage} start --log debug --user root --pass root memory
./db/manage.ts migrate
./db/manage.ts execute --file ./db/seeds/dev.surql
./db/manage.ts info
'';
db_persist.exec = ''
mkdir -p dev_data
chmod 777 dev_data
docker run -d -u root --rm --pull always --name surrealdb -p 8000:8000 -v ./dev_data:/dev_data ${surrealImage} start file:/dev_data/dev.db -u root -p root
# surreal start -u root -p root file:./dev_data/dev.db
'';
};
pre-commit.hooks = {
eslint.enable = true;
prettier.enable = true;
prettier.settings = {
configPath = ".prettierrc";
};
};
languages = {
javascript = {
enable = true;
bun.enable = true;
npm.enable = true;
};
typescript.enable = true;
deno.enable = true;
};
};
};
flake = {
# The usual flake attributes can be defined here, including system-
# agnostic ones like nixosModule and system-enumerating ones, although
# those are more easily expressed in perSystem.
};
};
}
This diff is collapsed.
document.documentElement.classList.toggle(
'dark',
localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
)
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
import { zodClient } from 'sveltekit-superforms/adapters';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import { ShieldAlert } from 'lucide-svelte';
import { userSignInSchema } from '$lib/zod';
......@@ -19,7 +18,6 @@
</script>
<form method="POST" {action} use:enhance>
<SuperDebug data={$form} />
{#if $errors._errors}
<aside class="alert variant-filled-error mt-6">
<ShieldAlert />
......
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import { userSignUpSchema } from '$lib/zod';
import { zodClient } from 'sveltekit-superforms/adapters';
......@@ -19,7 +18,6 @@
</script>
<form method="POST" {action} use:enhance>
<SuperDebug data={$form} />
<div class="mt-6">
<label class="label">
<span class="sr-only">Name</span>
......
<script>
import { browser } from '$app/environment';
import { getTheme } from '$lib/stores/theme';
import { Switch } from '@skeletonlabs/skeleton-svelte';
import { Moon, Sun } from 'lucide-svelte';
let mode = $state(getTheme() === 'dark');
let theme = $derived(mode ? 'dark' : 'light');
$effect(() => {
if (!browser) return;
localStorage.setItem('theme', theme);
document.documentElement.classList.toggle('dark', theme === 'dark');
});
function toggleTheme() {
// theme= theme == 'dark' ? 'light' : 'dark';
// TODO move stuff from $effect here?
}
function resetTheme() {
localStorage.removeItem('theme');
}
</script>
<Switch
name="dark-mode"
bind:checked={mode}
onchange={toggleTheme}
controlWidth="w-9"
controlActive="bg-tertiary-200"
compact
>
{#snippet inactiveChild()}
<Moon />
{/snippet}
{#snippet activeChild()}
<Sun />
{/snippet}
</Switch>
import { z } from 'zod';
import { db } from '$lib/server/db';
import {
moduleCreateSchema,
assignmentCreateSchema,
userCreateSchema,
submissionCreateSchema,
studentCreateSchema,
type ModuleCreate,
type AssignmentCreate,
type StudentCreate,
type SubmissionCreate
} from '$lib/surreal/schema';
export const parseAndCreateExamData = async (data: unknown) => {
const validatedData = examDataSchema.parse(data);
const surreal = db.getConnection();
// TODO use proper authentication, restrict to admin users
// HACK
await surreal.signin({ username: "root", password: "root" })
let module = moduleCreateSchema.parse({
...validatedData.module,
reference: validatedData.module.module_reference,
ungraded: validatedData.module.pass_only,
})
const [moduleRecord] = await surreal.create<ModuleCreate>('module', module)
const moduleId = moduleRecord?.id
console.log(moduleId)
const assignments = validatedData.submission_types.map(st =>
assignmentCreateSchema.parse({
description: st.description,
full_score: st.full_score,
module: moduleId,
name: st.name,
programming_language: st.programming_language,
solution: st.solution,
})
);
const assignmentRecords = await surreal.insert<AssignmentCreate>('assignment', assignments)
const assignmentMap = new Map(assignmentRecords.map(a => [a.name, a.id]));
const studentRecords = await Promise.all(
validatedData.students.map(async s => {
const studentData = studentCreateSchema.parse({ alias: s.identifier });
const [studentRecord] = await surreal.create<StudentCreate>('student', studentData);
return {
studentId: studentRecord.id,
originalData: s
};
})
);
const submissions = studentRecords.flatMap(({ studentId, originalData }) =>
originalData.submissions.map(sub =>
submissionCreateSchema.parse({
...sub,
source_code: sub.source_code ?? undefined, // from null to option/NONE
in: studentId,
out: assignmentMap.get(sub.type)
})
)
);
const submissionsResult = await surreal.insert_relation<SubmissionCreate>('submission', submissions);
return {
moduleId,
assignmentIds: Array.from(assignmentMap.values()),
studentIds: studentRecords.map(r => r.studentId),
submissionIds: submissionsResult.map(s => s.id)
};
}
const examDataSchema = z.object({
module: z.object({
module_reference: z.string(),
total_score: z.number(),
pass_score: z.number(),
pass_only: z.boolean()
}).passthrough(),
submission_types: z.array(z.object({
name: z.string(),
full_score: z.number(),
description: z.string(),
solution: z.string(),
programming_language: z.string()
}).passthrough()),
students: z.array(z.object({
fullname: z.string(),
identifier: z.string(),
username: z.string(),
email: z.string(),
exercise_groups: z.array(z.string()),
submissions: z.array(z.object({
code: z.string(),
source_code: z.string().nullable(),
type: z.string(),
tests: z.array(z.object({
name: z.string(),
annotation: z.string(),
label: z.string()
}).passthrough())
}).passthrough())
}).passthrough())
}).passthrough();
// Custom schema for transformed data
const transformedDataSchema = z.object({
module: moduleCreateSchema,
assignments: z.array(assignmentCreateSchema),
users: z.array(userCreateSchema),
students: z.array(studentCreateSchema),
submissions: z.array(submissionCreateSchema)
});
type TransformedData = z.infer<typeof transformedDataSchema>;
export const transformExamData = (data: unknown): any => {
return "nope";
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment