// SQLite storage for waitlist signups. One row per unique email — dedup is // enforced by the DB itself (UNIQUE, case-insensitive), so a repeat signup is a // no-op rather than a duplicate row. import Database from "better-sqlite3"; import path from "node:path"; import fs from "node:fs"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DATA_DIR = path.join(__dirname, "..", "data"); const DB_PATH = process.env.DB_PATH || path.join(DATA_DIR, "waitlist.db"); fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); const db = new Database(DB_PATH); db.pragma("journal_mode = WAL"); db.exec(` CREATE TABLE IF NOT EXISTS signups ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE COLLATE NOCASE, source TEXT, ip TEXT, user_agent TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) ); `); const insertStmt = db.prepare( `INSERT INTO signups (email, source, ip, user_agent) VALUES (@email, @source, @ip, @user_agent) ON CONFLICT(email) DO NOTHING` ); const countStmt = db.prepare(`SELECT COUNT(*) AS n FROM signups`); const listStmt = db.prepare( `SELECT id, email, source, ip, created_at FROM signups ORDER BY created_at DESC, id DESC` ); /** * Add an email. Returns { added: true } on a new signup, { added: false } if the * email was already on the list (deduped — no duplicate stored). */ export function addEmail({ email, source = null, ip = null, userAgent = null }) { const info = insertStmt.run({ email, source, ip, user_agent: userAgent }); return { added: info.changes > 0 }; } export function countEmails() { return countStmt.get().n; } export function listEmails() { return listStmt.all(); } export default db;