-- Portable SQL: TEXT ids and ISO-8601 timestamps, INTEGER booleans, TEXT JSON. CREATE TABLE user_roles ( user_id TEXT NOT NULL, role TEXT NOT NULL, PRIMARY KEY (user_id, role) ); CREATE TABLE groups (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE); CREATE TABLE group_members ( group_id TEXT NOT NULL, user_id TEXT NOT NULL, PRIMARY KEY (group_id, user_id) ); CREATE TABLE course_authors ( course_id TEXT NOT NULL, user_id TEXT NOT NULL, PRIMARY KEY (course_id, user_id) ); CREATE TABLE course_acl ( course_id TEXT NOT NULL, principal_type TEXT NOT NULL CHECK (principal_type IN ('user', 'group')), principal_id TEXT NOT NULL, PRIMARY KEY (course_id, principal_type, principal_id) ); CREATE TABLE progress ( user_id TEXT NOT NULL, course_id TEXT NOT NULL, lesson_id TEXT NOT NULL, state TEXT NOT NULL CHECK (state IN ('not-started', 'in-progress', 'done')), position_seconds DOUBLE PRECISION, updated_at TEXT NOT NULL, PRIMARY KEY (user_id, course_id, lesson_id) ); CREATE TABLE notes ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, course_id TEXT NOT NULL, lesson_id TEXT NOT NULL, anchor_kind TEXT NOT NULL CHECK (anchor_kind IN ('none', 'video', 'slide')), anchor_value TEXT, deck_id TEXT, body TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE INDEX notes_by_lesson ON notes (user_id, course_id, lesson_id); CREATE TABLE exercise_attempts ( user_id TEXT NOT NULL, course_id TEXT NOT NULL, lesson_id TEXT NOT NULL, track TEXT NOT NULL, code TEXT NOT NULL, result_json TEXT, passed INTEGER NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (user_id, course_id, lesson_id, track) ); CREATE TABLE user_api_keys ( user_id TEXT NOT NULL, provider TEXT NOT NULL, ciphertext TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (user_id, provider) ); CREATE TABLE ai_settings ( scope TEXT NOT NULL CHECK (scope IN ('server', 'user')), owner_id TEXT NOT NULL, -- '' for server scope mentor_off INTEGER NOT NULL, config_json TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY (scope, owner_id) ); CREATE TABLE ai_conversations ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, course_id TEXT NOT NULL, title TEXT, created_at TEXT NOT NULL ); CREATE TABLE ai_messages ( id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, created_at TEXT NOT NULL ); CREATE INDEX ai_messages_by_conversation ON ai_messages (conversation_id); CREATE TABLE library_settings (key TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE collections (id TEXT PRIMARY KEY, title TEXT NOT NULL, sort INTEGER NOT NULL); CREATE TABLE collection_courses ( collection_id TEXT NOT NULL, course_id TEXT NOT NULL, sort INTEGER NOT NULL, PRIMARY KEY (collection_id, course_id) ); CREATE TABLE jobs ( id TEXT PRIMARY KEY, kind TEXT NOT NULL, status TEXT NOT NULL, progress DOUBLE PRECISION NOT NULL, message TEXT, payload_json TEXT NOT NULL, result_json TEXT, error TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE INDEX jobs_by_status ON jobs (status);