PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); $pdo->exec('PRAGMA journal_mode=WAL'); $pdo->exec('PRAGMA busy_timeout=5000'); $pdo->exec('PRAGMA foreign_keys=ON'); golearn_migrate($pdo); return $pdo; } /** * Идемпотентная init-миграция схемы (IF NOT EXISTS). */ function golearn_migrate(PDO $pdo): void { $pdo->exec( 'CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, email TEXT UNIQUE COLLATE NOCASE, pass_hash TEXT NOT NULL, created_at TEXT NOT NULL )' ); $pdo->exec( 'CREATE TABLE IF NOT EXISTS progress ( user_id INTEGER PRIMARY KEY REFERENCES users(id), payload TEXT NOT NULL, updated_at TEXT NOT NULL )' ); $pdo->exec( 'CREATE TABLE IF NOT EXISTS rate_log ( ip TEXT NOT NULL, action TEXT NOT NULL, ts INTEGER NOT NULL )' ); $pdo->exec( 'CREATE INDEX IF NOT EXISTS idx_rate_log_ip_action_ts ON rate_log (ip, action, ts)' ); // Этап 8.1: токены восстановления пароля (аддитивно, IF NOT EXISTS). $pdo->exec( 'CREATE TABLE IF NOT EXISTS password_resets ( id INTEGER PRIMARY KEY, email TEXT NOT NULL, token_hash TEXT NOT NULL, expires_at INTEGER NOT NULL )' ); $pdo->exec( 'CREATE INDEX IF NOT EXISTS idx_password_resets_token_hash ON password_resets (token_hash)' ); }