import { readFileSync, readdirSync } from "fs"; import { join } from "path"; import { Pool } from "pg"; import { config } from "dotenv"; config(); const pool = new Pool({ host: process.env.POSTGRES_HOST || "localhost", port: parseInt(process.env.POSTGRES_PORT || "5432"), database: process.env.POSTGRES_DB || "transceiver_db", user: process.env.POSTGRES_USER || "tip", password: process.env.POSTGRES_PASSWORD || "tip_dev_2026", }); async function migrate(): Promise { const sqlDir = join(__dirname, "..", "sql"); const files = readdirSync(sqlDir) .filter((f) => f.endsWith(".sql")) .sort(); const client = await pool.connect(); try { // Create migration tracker if it doesn't exist await client.query(` CREATE TABLE IF NOT EXISTS _migrations ( filename TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) `); // Get already-applied migrations const { rows } = await client.query( "SELECT filename FROM _migrations ORDER BY filename" ); const applied = new Set(rows.map((r: { filename: string }) => r.filename)); const pending = files.filter((f) => !applied.has(f)); if (pending.length === 0) { console.log("All migrations already applied. Nothing to do."); return; } console.log( `${applied.size} already applied, running ${pending.length} pending migrations` ); for (const file of pending) { const sql = readFileSync(join(sqlDir, file), "utf-8"); console.log(`Running: ${file}...`); await client.query("BEGIN"); try { await client.query(sql); await client.query( "INSERT INTO _migrations (filename) VALUES ($1)", [file] ); await client.query("COMMIT"); console.log(` Done: ${file}`); } catch (err) { await client.query("ROLLBACK"); throw err; } } console.log("\nAll migrations completed successfully."); } catch (err) { console.error("Migration failed:", err); process.exit(1); } finally { client.release(); await pool.end(); } } migrate();