import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; import Database from "better-sqlite3"; import { SQLITE_PATH } from "./config.js"; mkdirSync(dirname(SQLITE_PATH), { recursive: true }); const db = new Database(SQLITE_PATH); db.pragma("journal_mode = WAL"); db.pragma("foreign_keys = ON"); db.pragma("busy_timeout = 5000"); db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, google_sub TEXT NOT NULL UNIQUE, email TEXT NOT NULL, name TEXT, picture TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS oauth_tokens ( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, access_token TEXT NOT NULL, refresh_token TEXT, expiry INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS watch_state ( user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, history_id TEXT NOT NULL, expiration INTEGER NOT NULL ); `); const getUserByIdStmt = db.prepare(`SELECT * FROM users WHERE id = ?`); const getUserByEmailStmt = db.prepare( `SELECT * FROM users WHERE LOWER(email) = LOWER(?)`, ); const getTokensStmt = db.prepare(`SELECT * FROM oauth_tokens WHERE user_id = ?`); const listUsersWithTokensStmt = db.prepare(` SELECT u.* FROM users u INNER JOIN oauth_tokens t ON t.user_id = u.id `); const saveTokensStmt = db.prepare(` INSERT INTO oauth_tokens (user_id, access_token, refresh_token, expiry) VALUES (@userId, @accessToken, @refreshToken, @expiry) ON CONFLICT(user_id) DO UPDATE SET access_token = excluded.access_token, refresh_token = COALESCE(excluded.refresh_token, oauth_tokens.refresh_token), expiry = excluded.expiry `); const getWatchStateStmt = db.prepare( `SELECT * FROM watch_state WHERE user_id = ?`, ); const saveWatchStateStmt = db.prepare(` INSERT INTO watch_state (user_id, history_id, expiration) VALUES (@userId, @historyId, @expiration) ON CONFLICT(user_id) DO UPDATE SET history_id = excluded.history_id, expiration = excluded.expiration `); export function getUserById(id) { return getUserByIdStmt.get(id) || null; } export function getUserByEmail(email) { if (!email) return null; return getUserByEmailStmt.get(email) || null; } export function getTokens(userId) { return getTokensStmt.get(userId) || null; } export function listUsersWithTokens() { return listUsersWithTokensStmt.all(); } export function saveTokens(userId, { accessToken, refreshToken, expiry }) { saveTokensStmt.run({ userId, accessToken, refreshToken: refreshToken || null, expiry, }); } export function getWatchState(userId) { return getWatchStateStmt.get(userId) || null; } export function saveWatchState(userId, { historyId, expiration }) { saveWatchStateStmt.run({ userId, historyId: String(historyId), expiration: Number(expiration), }); }