import fs from "node:fs"; import pg from "pg"; import { drizzle } from "drizzle-orm/node-postgres"; import * as schema from "./schema/index.js"; export type Database = ReturnType["db"]; let sharedPool: pg.Pool | undefined; function resolveSsl(url: string): pg.PoolConfig["ssl"] | undefined { const caPath = process.env.DATABASE_CA_CERT; if (caPath && fs.existsSync(caPath)) { return { ca: fs.readFileSync(caPath, "utf8"), rejectUnauthorized: true }; } if (/sslmode=(require|verify)/.test(url)) { return { rejectUnauthorized: false }; } return undefined; } function stripSslmode(url: string): string { try { const u = new URL(url); u.searchParams.delete("sslmode"); return u.toString(); } catch { return url.replace(/[?&]sslmode=[^&]+/, "").replace(/\?$/, ""); } } /** * Skapar en databasklient. API och worker delar mönster men äger varsin pool. * DATABASE_URL pekar på appens separata databas med egen minimalprivilegie-användare * (minsta möjliga privilegier, spec §52). */ export function createDatabase(connectionString?: string) { const isTest = process.env.NODE_ENV === "test" || process.env.VITEST !== undefined; const rawUrl = connectionString ?? (isTest ? process.env.TEST_DATABASE_URL : process.env.DATABASE_URL); if (!rawUrl) { if (isTest) { throw new Error( "Sätt TEST_DATABASE_URL till en dedikerad testdatabas. Tester får aldrig använda DATABASE_URL.", ); } throw new Error("Missing DATABASE_URL. Set it in your .env or environment before starting the app."); } const url = stripSslmode(rawUrl); const pool = new pg.Pool({ connectionString: url, max: Number(process.env.DATABASE_POOL_MAX ?? 10), idleTimeoutMillis: 30_000, connectionTimeoutMillis: 10_000, ssl: resolveSsl(rawUrl), }); const db = drizzle(pool, { schema, casing: "snake_case" }); return { db, pool }; } /** Singleton för processer som bara behöver en anslutning. */ export function getDatabase() { if (!sharedPool) { const { db, pool } = createDatabase(); sharedPool = pool; sharedDb = db; } return sharedDb!; } let sharedDb: Database | undefined; export async function closeDatabase(): Promise { if (sharedPool) { await sharedPool.end(); sharedPool = undefined; sharedDb = undefined; } } export { schema };