feat(db): Postgres backend + dialect-portable queries + data migration (Phase 5)

- 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>
This commit is contained in:
2026-07-24 22:28:04 +05:30
parent 3250530596
commit e67a783bdc
5 changed files with 170 additions and 45 deletions
+34 -26
View File
@@ -36,34 +36,42 @@ const server = http.createServer((req, res) => {
const wss = new WebSocketServer({ server, path: '/ws' });
wss.on('connection', onConnection);
server.listen(PORT, () => {
console.log(`HTTP on http://localhost:${PORT}`);
try { require('./media').backfill(); } catch (e) {} // streaming renditions for older video uploads
});
// Apply the DB schema BEFORE serving. For Postgres this creates the tables (async); for SQLite it's a
// no-op (the schema is already applied synchronously when db.js is required). Serving only starts once the
// store is ready, so the first request can never hit a missing table.
const db = require('./dbx');
// HTTPS — required so other devices can share their screen (browsers block
// screen capture on non-secure origins). Uses cert.pem/key.pem if present.
let httpsServer = null;
try {
const certPath = path.join(__dirname, 'cert.pem');
const keyPath = path.join(__dirname, 'key.pem');
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
httpsServer = https.createServer(
{ cert: fs.readFileSync(certPath), key: fs.readFileSync(keyPath) },
(req, res) => server.emit('request', req, res)
);
const wssSecure = new WebSocketServer({ server: httpsServer, path: '/ws' });
wssSecure.on('connection', onConnection);
httpsServer.listen(HTTPS_PORT, () => {
console.log(`HTTPS on https://localhost:${HTTPS_PORT} (use this address from other devices)`);
console.log(` End user shares screen: https://<this-pc-ip>:${HTTPS_PORT}/share`);
console.log(` Technician connects: https://<this-pc-ip>:${HTTPS_PORT}/connect`);
});
} else {
console.log('(No cert.pem/key.pem found — HTTPS disabled. Other devices can view but not share their screen.)');
function startListening() {
server.listen(PORT, () => {
console.log(`HTTP on http://localhost:${PORT} (db=${db.name})`);
try { require('./media').backfill(); } catch (e) {} // streaming renditions for older video uploads
});
// HTTPS — required so other devices can share their screen (browsers block
// screen capture on non-secure origins). Uses cert.pem/key.pem if present.
try {
const certPath = path.join(__dirname, 'cert.pem');
const keyPath = path.join(__dirname, 'key.pem');
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
const httpsServer = https.createServer(
{ cert: fs.readFileSync(certPath), key: fs.readFileSync(keyPath) },
(req, res) => server.emit('request', req, res)
);
const wssSecure = new WebSocketServer({ server: httpsServer, path: '/ws' });
wssSecure.on('connection', onConnection);
httpsServer.listen(HTTPS_PORT, () => {
console.log(`HTTPS on https://localhost:${HTTPS_PORT} (use this address from other devices)`);
console.log(` End user shares screen: https://<this-pc-ip>:${HTTPS_PORT}/share`);
console.log(` Technician connects: https://<this-pc-ip>:${HTTPS_PORT}/connect`);
});
} else {
console.log('(No cert.pem/key.pem found — HTTPS disabled. Other devices can view but not share their screen.)');
}
} catch (e) {
console.log('HTTPS failed to start:', e.message);
}
} catch (e) {
console.log('HTTPS failed to start:', e.message);
}
db.init().then(startListening).catch((e) => { console.error('DB init failed:', (e && e.message) || e); process.exit(1); });
module.exports = { server };