From 9f942a0e56494728b6cbc9d0ca47519cb4e90405 Mon Sep 17 00:00:00 2001 From: hafroese Date: Thu, 2 Apr 2026 23:01:33 +0200 Subject: [PATCH] feat: database schema - users, decks, cards, progress, tutorials, quiz results --- setup/schema.sql | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 setup/schema.sql diff --git a/setup/schema.sql b/setup/schema.sql new file mode 100644 index 0000000..35572aa --- /dev/null +++ b/setup/schema.sql @@ -0,0 +1,99 @@ +-- edu.senex.de Training Platform - Database Schema +-- Run: docker exec -i postgres psql -U postgres -d edu < schema.sql + +-- Users +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + display_name VARCHAR(100) NOT NULL, + is_admin BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Flashcard Decks +CREATE TABLE IF NOT EXISTS decks ( + id SERIAL PRIMARY KEY, + slug VARCHAR(100) UNIQUE NOT NULL, + name VARCHAR(200) NOT NULL, + description TEXT DEFAULT '', + card_count INT DEFAULT 0, + sort_order INT DEFAULT 0, + source_file VARCHAR(200), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Flashcards +CREATE TABLE IF NOT EXISTS cards ( + id SERIAL PRIMARY KEY, + deck_id INT NOT NULL REFERENCES decks(id) ON DELETE CASCADE, + question TEXT NOT NULL, + answer TEXT NOT NULL, + level VARCHAR(20) DEFAULT 'basis', + sort_order INT DEFAULT 0, + source_file VARCHAR(200), + updated_at TIMESTAMP DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_cards_deck ON cards(deck_id); + +-- SM-2 Spaced Repetition Progress +CREATE TABLE IF NOT EXISTS card_progress ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + card_id INT NOT NULL REFERENCES cards(id) ON DELETE CASCADE, + ease_factor REAL DEFAULT 2.5, + interval_days INT DEFAULT 0, + repetitions INT DEFAULT 0, + next_review DATE DEFAULT CURRENT_DATE, + last_rating VARCHAR(10), + last_reviewed_at TIMESTAMP, + UNIQUE(user_id, card_id) +); +CREATE INDEX IF NOT EXISTS idx_card_progress_due ON card_progress(user_id, next_review); + +-- Tutorials +CREATE TABLE IF NOT EXISTS tutorials ( + id SERIAL PRIMARY KEY, + slug VARCHAR(100) UNIQUE NOT NULL, + title VARCHAR(200) NOT NULL, + description TEXT DEFAULT '', + content_md TEXT NOT NULL, + duration_min INT, + sort_order INT DEFAULT 0, + source_file VARCHAR(200), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Tutorial Progress +CREATE TABLE IF NOT EXISTS tutorial_progress ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + tutorial_id INT NOT NULL REFERENCES tutorials(id) ON DELETE CASCADE, + completed BOOLEAN DEFAULT false, + completed_at TIMESTAMP, + UNIQUE(user_id, tutorial_id) +); + +-- Cheat Sheets +CREATE TABLE IF NOT EXISTS cheatsheets ( + id SERIAL PRIMARY KEY, + slug VARCHAR(100) UNIQUE NOT NULL, + title VARCHAR(200) NOT NULL, + category VARCHAR(100) DEFAULT '', + content_md TEXT NOT NULL, + source_file VARCHAR(200), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Quiz Results +CREATE TABLE IF NOT EXISTS quiz_results ( + id SERIAL PRIMARY KEY, + user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + deck_slug VARCHAR(100), + quiz_type VARCHAR(50) DEFAULT 'multiple_choice', + score_percent INT, + total_questions INT, + correct_answers INT, + created_at TIMESTAMP DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_quiz_results_user ON quiz_results(user_id, created_at DESC);