import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import dotenv from "dotenv"; import pg from "pg"; import bcrypt from "bcryptjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); dotenv.config({ path: path.resolve(__dirname, "../../../apps/web/.env") }); const { Pool } = pg; function getDatabaseName(connectionString) { try { const url = new URL(connectionString); const name = url.pathname.replace(/^\/+/, ""); return name ? decodeURIComponent(name) : ""; } catch { return ""; } } function requireAllowedDatabase() { if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL is required"); const allowedRaw = process.env.ALLOWED_DB_NAMES; if (!allowedRaw) throw new Error("ALLOWED_DB_NAMES is required"); const allowed = allowedRaw .split(",") .map(value => value.trim().toLowerCase()) .filter(Boolean); if (!allowed.length) throw new Error("ALLOWED_DB_NAMES must include at least one database name"); const dbName = getDatabaseName(process.env.DATABASE_URL); if (!dbName) throw new Error("DATABASE_URL must include a database name"); if (!allowed.includes(dbName.toLowerCase())) throw new Error(`DATABASE_URL must target an allowed database. Found "${dbName}"`); } async function main() { requireAllowedDatabase(); const sqlPath = path.resolve(__dirname, "../../../docs/seed-ui.sql"); let sql = fs.readFileSync(sqlPath, "utf8"); if (!sql.includes("__PASSWORD_HASH__")) throw new Error("Seed SQL missing __PASSWORD_HASH__ placeholder"); const seedPassword = process.env.SEED_PASSWORD || "FiddyDev123!"; const passwordHash = await bcrypt.hash(seedPassword, 12); sql = sql.replaceAll("__PASSWORD_HASH__", passwordHash); const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: process.env.DATABASE_SSL === "false" ? false : { rejectUnauthorized: false } }); const client = await pool.connect(); try { await client.query("begin"); await client.query(sql); await client.query("commit"); const tables = [ "users", "groups", "group_members", "group_settings", "group_join_requests", "group_invite_links", "group_audit_log", "tags", "entries", "entry_tags", "receipts" ]; const counts = {}; for (const table of tables) { const { rows } = await client.query(`select count(*)::int as count from ${table}`); counts[table] = rows[0]?.count ?? 0; } const { rows: userRows } = await client.query("select email from users order by id"); const { rows: groupRows } = await client.query("select name from groups order by id"); console.log("UI seed complete."); console.log("Counts:"); for (const [table, count] of Object.entries(counts)) console.log(`- ${table}: ${count}`); console.log("Users:"); for (const row of userRows) console.log(`- ${row.email}`); console.log("Groups:"); for (const row of groupRows) console.log(`- ${row.name}`); } catch (error) { await client.query("rollback"); console.error(error); process.exitCode = 1; } finally { client.release(); await pool.end(); } } main();