-- 001_init.sql PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_salt TEXT NOT NULL, password_hash TEXT NOT NULL, rating INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS sessions ( token TEXT PRIMARY KEY, user_id INTEGER NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_experience ( user_id INTEGER PRIMARY KEY, xp INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_experience_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, xp_delta INTEGER NOT NULL, rating_before INTEGER NOT NULL DEFAULT 0, rating_after INTEGER NOT NULL DEFAULT 0, source TEXT NOT NULL DEFAULT 'users.rating', note TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS problems ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT NOT NULL UNIQUE, title TEXT NOT NULL, statement_md TEXT NOT NULL, difficulty INTEGER NOT NULL DEFAULT 1, source TEXT NOT NULL DEFAULT '', statement_url TEXT NOT NULL DEFAULT '', llm_profile_json TEXT NOT NULL DEFAULT '{}', sample_input TEXT NOT NULL DEFAULT '', sample_output TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS problem_tags ( problem_id INTEGER NOT NULL, tag TEXT NOT NULL, PRIMARY KEY(problem_id, tag), FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS submissions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, problem_id INTEGER NOT NULL, contest_id INTEGER, language TEXT NOT NULL, code TEXT NOT NULL, status TEXT NOT NULL, score INTEGER NOT NULL DEFAULT 0, time_ms INTEGER NOT NULL DEFAULT 0, memory_kb INTEGER NOT NULL DEFAULT 0, compile_log TEXT NOT NULL DEFAULT '', runtime_log TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE, FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS wrong_book ( user_id INTEGER NOT NULL, problem_id INTEGER NOT NULL, last_submission_id INTEGER, note TEXT NOT NULL DEFAULT '', updated_at INTEGER NOT NULL, PRIMARY KEY(user_id, problem_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE, FOREIGN KEY(last_submission_id) REFERENCES submissions(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS contests ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, starts_at INTEGER NOT NULL, ends_at INTEGER NOT NULL, rule_json TEXT NOT NULL DEFAULT '{}' ); CREATE TABLE IF NOT EXISTS contest_problems ( contest_id INTEGER NOT NULL, problem_id INTEGER NOT NULL, idx INTEGER NOT NULL, PRIMARY KEY(contest_id, problem_id), FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS contest_registrations ( contest_id INTEGER NOT NULL, user_id INTEGER NOT NULL, registered_at INTEGER NOT NULL, PRIMARY KEY(contest_id, user_id), FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS contest_modifiers ( id INTEGER PRIMARY KEY AUTOINCREMENT, contest_id INTEGER NOT NULL, code TEXT NOT NULL, title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', rule_json TEXT NOT NULL DEFAULT '{}', is_active INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, FOREIGN KEY(contest_id) REFERENCES contests(id) ON DELETE CASCADE, UNIQUE(contest_id, code) ); CREATE TABLE IF NOT EXISTS seasons ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL UNIQUE, title TEXT NOT NULL, starts_at INTEGER NOT NULL, ends_at INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'draft', pass_json TEXT NOT NULL DEFAULT '{}', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS season_reward_tracks ( id INTEGER PRIMARY KEY AUTOINCREMENT, season_id INTEGER NOT NULL, tier_no INTEGER NOT NULL, required_xp INTEGER NOT NULL DEFAULT 0, reward_type TEXT NOT NULL DEFAULT 'free', reward_value INTEGER NOT NULL DEFAULT 0, reward_meta_json TEXT NOT NULL DEFAULT '{}', FOREIGN KEY(season_id) REFERENCES seasons(id) ON DELETE CASCADE, UNIQUE(season_id, tier_no, reward_type) ); CREATE TABLE IF NOT EXISTS season_user_progress ( season_id INTEGER NOT NULL, user_id INTEGER NOT NULL, xp INTEGER NOT NULL DEFAULT 0, level INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL, PRIMARY KEY(season_id, user_id), FOREIGN KEY(season_id) REFERENCES seasons(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS season_reward_claims ( id INTEGER PRIMARY KEY AUTOINCREMENT, season_id INTEGER NOT NULL, user_id INTEGER NOT NULL, tier_no INTEGER NOT NULL, reward_type TEXT NOT NULL DEFAULT 'free', claimed_at INTEGER NOT NULL, FOREIGN KEY(season_id) REFERENCES seasons(id) ON DELETE CASCADE, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(season_id, user_id, tier_no, reward_type) ); CREATE TABLE IF NOT EXISTS loot_drop_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, source_type TEXT NOT NULL, source_id INTEGER NOT NULL DEFAULT 0, item_code TEXT NOT NULL, item_name TEXT NOT NULL DEFAULT '', rarity TEXT NOT NULL DEFAULT 'common', amount INTEGER NOT NULL DEFAULT 0, meta_json TEXT NOT NULL DEFAULT '{}', created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS kb_articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT NOT NULL UNIQUE, title TEXT NOT NULL, content_md TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS kb_article_links ( article_id INTEGER NOT NULL, problem_id INTEGER NOT NULL, PRIMARY KEY(article_id, problem_id), FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS kb_knowledge_claims ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, article_id INTEGER NOT NULL, knowledge_key TEXT NOT NULL, reward INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE, UNIQUE(user_id, article_id, knowledge_key) ); CREATE TABLE IF NOT EXISTS kb_weekly_tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, week_key TEXT NOT NULL, article_id INTEGER NOT NULL, article_slug TEXT NOT NULL, article_title TEXT NOT NULL, knowledge_key TEXT NOT NULL, knowledge_title TEXT NOT NULL, knowledge_description TEXT NOT NULL DEFAULT '', difficulty TEXT NOT NULL DEFAULT 'bronze', reward INTEGER NOT NULL DEFAULT 1, prerequisites TEXT NOT NULL DEFAULT '', order_no INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, completed_at INTEGER, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(article_id) REFERENCES kb_articles(id) ON DELETE CASCADE, UNIQUE(user_id, week_key, article_id, knowledge_key) ); CREATE TABLE IF NOT EXISTS kb_weekly_bonus_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, week_key TEXT NOT NULL, reward INTEGER NOT NULL DEFAULT 100, created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(user_id, week_key) ); CREATE TABLE IF NOT EXISTS import_jobs ( id INTEGER PRIMARY KEY AUTOINCREMENT, status TEXT NOT NULL, trigger TEXT NOT NULL DEFAULT 'manual', total_count INTEGER NOT NULL DEFAULT 0, processed_count INTEGER NOT NULL DEFAULT 0, success_count INTEGER NOT NULL DEFAULT 0, failed_count INTEGER NOT NULL DEFAULT 0, options_json TEXT NOT NULL DEFAULT '{}', last_error TEXT NOT NULL DEFAULT '', started_at INTEGER NOT NULL, finished_at INTEGER, updated_at INTEGER NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS import_job_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, job_id INTEGER NOT NULL, source_path TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'queued', title TEXT NOT NULL DEFAULT '', difficulty INTEGER NOT NULL DEFAULT 0, problem_id INTEGER, error_text TEXT NOT NULL DEFAULT '', started_at INTEGER, finished_at INTEGER, updated_at INTEGER NOT NULL, created_at INTEGER NOT NULL, FOREIGN KEY(job_id) REFERENCES import_jobs(id) ON DELETE CASCADE, UNIQUE(job_id, source_path) ); CREATE TABLE IF NOT EXISTS crawler_targets ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, normalized_url TEXT NOT NULL UNIQUE, status TEXT NOT NULL DEFAULT 'queued', submit_source TEXT NOT NULL DEFAULT 'manual', submitter_id TEXT NOT NULL DEFAULT '', submitter_name TEXT NOT NULL DEFAULT '', rule_json TEXT NOT NULL DEFAULT '{}', script_path TEXT NOT NULL DEFAULT '', last_error TEXT NOT NULL DEFAULT '', last_test_at INTEGER, last_run_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS crawler_runs ( id INTEGER PRIMARY KEY AUTOINCREMENT, target_id INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'queued', http_status INTEGER NOT NULL DEFAULT 0, output_json TEXT NOT NULL DEFAULT '{}', error_text TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, FOREIGN KEY(target_id) REFERENCES crawler_targets(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS problem_drafts ( user_id INTEGER NOT NULL, problem_id INTEGER NOT NULL, language TEXT NOT NULL DEFAULT 'cpp', code TEXT NOT NULL DEFAULT '', stdin TEXT NOT NULL DEFAULT '', updated_at INTEGER NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY(user_id, problem_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS problem_solution_jobs ( id INTEGER PRIMARY KEY AUTOINCREMENT, problem_id INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'queued', progress INTEGER NOT NULL DEFAULT 0, message TEXT NOT NULL DEFAULT '', created_by INTEGER NOT NULL DEFAULT 0, max_solutions INTEGER NOT NULL DEFAULT 3, created_at INTEGER NOT NULL, started_at INTEGER, finished_at INTEGER, updated_at INTEGER NOT NULL, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS problem_solutions ( id INTEGER PRIMARY KEY AUTOINCREMENT, problem_id INTEGER NOT NULL, variant INTEGER NOT NULL DEFAULT 1, title TEXT NOT NULL DEFAULT '', idea_md TEXT NOT NULL DEFAULT '', explanation_md TEXT NOT NULL DEFAULT '', code_cpp TEXT NOT NULL DEFAULT '', complexity TEXT NOT NULL DEFAULT '', tags_json TEXT NOT NULL DEFAULT '[]', source TEXT NOT NULL DEFAULT 'llm', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, FOREIGN KEY(problem_id) REFERENCES problems(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS source_crystal_settings ( id INTEGER PRIMARY KEY CHECK(id = 1), monthly_interest_rate REAL NOT NULL DEFAULT 0.02, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS source_crystal_accounts ( user_id INTEGER PRIMARY KEY, balance REAL NOT NULL DEFAULT 0, last_interest_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS source_crystal_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, tx_type TEXT NOT NULL, amount REAL NOT NULL, balance_after REAL NOT NULL, note TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_achievement_milestones ( user_id INTEGER NOT NULL, milestone_no INTEGER NOT NULL, completed_count_snapshot INTEGER NOT NULL DEFAULT 0, rating_bonus INTEGER NOT NULL DEFAULT 20, created_at INTEGER NOT NULL, PRIMARY KEY(user_id, milestone_no), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); INSERT OR IGNORE INTO source_crystal_settings(id, monthly_interest_rate, updated_at) VALUES(1, 0.02, strftime('%s','now')); INSERT OR IGNORE INTO user_experience(user_id, xp, updated_at) SELECT id, CASE WHEN rating > 0 THEN rating ELSE 0 END, strftime('%s','now') FROM users; CREATE TRIGGER IF NOT EXISTS trg_users_init_experience AFTER INSERT ON users BEGIN INSERT OR IGNORE INTO user_experience(user_id, xp, updated_at) VALUES(NEW.id, CASE WHEN NEW.rating > 0 THEN NEW.rating ELSE 0 END, strftime('%s','now')); INSERT INTO user_experience_logs(user_id, xp_delta, rating_before, rating_after, source, note, created_at) SELECT NEW.id, NEW.rating, 0, NEW.rating, 'users.insert', 'initial rating gain', strftime('%s','now') WHERE NEW.rating > 0; END; CREATE TRIGGER IF NOT EXISTS trg_users_rating_gain_to_experience AFTER UPDATE OF rating ON users WHEN NEW.rating > OLD.rating BEGIN INSERT OR IGNORE INTO user_experience(user_id, xp, updated_at) VALUES(NEW.id, 0, strftime('%s','now')); UPDATE user_experience SET xp = xp + (NEW.rating - OLD.rating), updated_at = strftime('%s','now') WHERE user_id = NEW.id; INSERT INTO user_experience_logs(user_id, xp_delta, rating_before, rating_after, source, note, created_at) VALUES(NEW.id, (NEW.rating - OLD.rating), OLD.rating, NEW.rating, 'users.rating', 'rating gain', strftime('%s','now')); END; CREATE INDEX IF NOT EXISTS idx_submissions_user_created_at ON submissions(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_submissions_problem_created_at ON submissions(problem_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_submissions_contest_user_created_at ON submissions(contest_id, user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_problem_tags_tag ON problem_tags(tag); CREATE INDEX IF NOT EXISTS idx_contest_modifiers_contest_active ON contest_modifiers(contest_id, is_active, id); CREATE INDEX IF NOT EXISTS idx_seasons_status_range ON seasons(status, starts_at, ends_at); CREATE INDEX IF NOT EXISTS idx_season_reward_tracks_season_tier ON season_reward_tracks(season_id, tier_no, required_xp); CREATE INDEX IF NOT EXISTS idx_season_user_progress_user ON season_user_progress(user_id, season_id, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_season_reward_claims_user ON season_reward_claims(user_id, season_id, claimed_at DESC); CREATE INDEX IF NOT EXISTS idx_loot_drop_logs_user_created ON loot_drop_logs(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_kb_article_links_problem_id ON kb_article_links(problem_id); CREATE INDEX IF NOT EXISTS idx_kb_knowledge_claims_user_article ON kb_knowledge_claims(user_id, article_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_kb_weekly_tasks_user_week_order ON kb_weekly_tasks(user_id, week_key, order_no, id); CREATE INDEX IF NOT EXISTS idx_kb_weekly_tasks_user_week_completed ON kb_weekly_tasks(user_id, week_key, completed_at); CREATE INDEX IF NOT EXISTS idx_kb_weekly_bonus_logs_user_week ON kb_weekly_bonus_logs(user_id, week_key); CREATE INDEX IF NOT EXISTS idx_import_jobs_created_at ON import_jobs(created_at DESC); CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON import_jobs(status, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_import_job_items_job_status ON import_job_items(job_id, status, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_crawler_targets_status_updated ON crawler_targets(status, updated_at ASC); CREATE INDEX IF NOT EXISTS idx_crawler_runs_target_created ON crawler_runs(target_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_problem_drafts_updated ON problem_drafts(updated_at DESC); CREATE INDEX IF NOT EXISTS idx_problem_solution_jobs_problem ON problem_solution_jobs(problem_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_problem_solutions_problem ON problem_solutions(problem_id, variant, id); CREATE INDEX IF NOT EXISTS idx_user_experience_logs_user_created ON user_experience_logs(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_source_crystal_tx_user_created ON source_crystal_transactions(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_user_achievement_milestones_user_created ON user_achievement_milestones(user_id, created_at DESC);