e67a783bdc
- db/pg.js: the pg backend (prepare/exec/tx/init) — ?→$N translation, BIGINT parsed as Number (matches sqlite; else expires_at<Date.now() compares string<number), transactions on one pooled client, init() applies schema.pg.sql. Same interface as db/sqlite.js, so repos are unchanged. - repos.js: the ~7 SQLite-only queries rewritten to run on BOTH engines — audit.add @named→positional; email lookups COLLATE NOCASE→LOWER()=LOWER(); INSERT OR IGNORE→ON CONFLICT DO NOTHING (addMember/poll vote/favorite); mergeInto's UPDATE OR IGNORE→UPDATE…WHERE NOT EXISTS/NOT IN and INSERT OR REPLACE→ON CONFLICT DO UPDATE. Re-validated on sqlite: db-smoke still 22/22. - server.js: boot now `await db.init()` before listening (pg creates tables; sqlite no-op), so the first request can't hit a missing table. - db/migrate-sqlite-to-pg.js: one-shot row copy in FK order (bulk insert, TRUNCATE first so re-runnable). audit_log id left to PG's identity. - package.json: add pg ^8.13.1. Next: validate DB_BACKEND=pg smoke against a real Postgres on the server, then merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>