Files
exbyte-depot-viewer-perforce/waitlist-server/lib/db.js
Bonchellon 3954082e8d v0.3.2 — commit/submit progress, cancel, thumbnails, waitlist landing+admin
Client:
- Commit: batch `p4 reopen` so huge changelists (63k+) no longer blow the
  Windows command-line limit; live "N / total" progress + spinner during commit.
- Submit dialog: white arrow, "N of total" + % bar, running file log,
  Cancel button (aborts pre-commit; changelist stays pending).
- Disk scan: streaming reconcile with a live "found N files" counter + Cancel.
- Thumbnails: only decode browser-renderable image formats; glyph fallback
  (+ onError) instead of a broken-image icon for bmp/tga/dds/etc.

Landing + waitlist:
- waitlist-server: Express + SQLite email collector (dedup) with a secure
  admin panel (scrypt auth, rate-limited login, signed cookies) and CSV export.
- landing form now POSTs signups to the waitlist API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:40:53 +03:00

57 lines
1.8 KiB
JavaScript

// 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;