"use strict"; /* ================= SQLite(node:sqlite 内置驱动,零依赖) ================= */ const { DatabaseSync } = require("node:sqlite"); const path = require("node:path"); const fs = require("node:fs"); const { expandHome } = require("./env"); /* 数据目录不放源码树:默认 ~/.starraid,可用 DATA_DIR 环境变量 / .env 覆盖 */ const DATA_DIR = expandHome(process.env.DATA_DIR || path.join(require("node:os").homedir(), ".starraid")); fs.mkdirSync(DATA_DIR, { recursive: true }); const db = new DatabaseSync(path.join(DATA_DIR, "starraid.db")); db.exec("PRAGMA journal_mode=WAL;"); db.exec("PRAGMA foreign_keys=ON;"); db.exec(` CREATE TABLE IF NOT EXISTS users( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE COLLATE NOCASE, email TEXT NOT NULL UNIQUE COLLATE NOCASE, pass_hash TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS sessions( token TEXT PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); CREATE TABLE IF NOT EXISTS user_stats( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, boss_kills INTEGER NOT NULL DEFAULT 0, endless_high INTEGER NOT NULL DEFAULT 0, runs INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS codex_unlocks( user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, entry_key TEXT NOT NULL, unlocked_at INTEGER NOT NULL, PRIMARY KEY(user_id, entry_key) ); CREATE TABLE IF NOT EXISTS runs( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, mode TEXT NOT NULL, diff INTEGER NOT NULL, score INTEGER NOT NULL, wave INTEGER NOT NULL, stage INTEGER NOT NULL, time_sec INTEGER NOT NULL, boss_kills INTEGER NOT NULL, win INTEGER NOT NULL, created_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_runs_user ON runs(user_id); CREATE TABLE IF NOT EXISTS player_state( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, state TEXT NOT NULL, updated_at INTEGER NOT NULL ); `); /* 定期清理过期 session */ setInterval(() => { try { db.prepare("DELETE FROM sessions WHERE expires_at