// Postgres connection + migration runner for the Platform DB. // // Migration pattern matches the one already established in the Lab image's // backend/server.js (check-before-alter, idempotent) — here it's file-based rather than // inline, since this is a fresh schema with a real migrations/ directory instead of a single // growing CREATE TABLE IF NOT EXISTS block. const fs = require('fs'); const path = require('path'); const { Pool } = require('pg'); const MIGRATIONS_DIR = path.join(__dirname, '../migrations'); let _pool = null; function getPool() { if (_pool) return _pool; _pool = new Pool({ connectionString: process.env.PLATFORM_DB_URL || 'postgres://postgres:postgres@localhost:5433/rootnode_platform', }); return _pool; } // Applies every .sql file in migrations/ that hasn't run yet, in filename order. // Tracked in schema_migrations so re-running is a no-op — same idempotency guarantee the // existing SQLite migrations already rely on. async function runMigrations() { const pool = getPool(); await pool.query(` CREATE TABLE IF NOT EXISTS schema_migrations ( version TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT now() ) `); const applied = new Set( (await pool.query('SELECT version FROM schema_migrations')).rows.map(r => r.version) ); const files = fs.readdirSync(MIGRATIONS_DIR).filter(f => f.endsWith('.sql')).sort(); const ranNow = []; for (const file of files) { if (applied.has(file)) continue; const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), 'utf8'); const client = await pool.connect(); try { await client.query('BEGIN'); await client.query(sql); await client.query('INSERT INTO schema_migrations (version) VALUES ($1)', [file]); await client.query('COMMIT'); ranNow.push(file); } catch (e) { await client.query('ROLLBACK'); throw new Error(`Migration ${file} failed: ${e.message}`); } finally { client.release(); } } return ranNow; } module.exports = { getPool, runMigrations };