const { Pool, Client } = require('pg') const fs = require('fs') const path = require('path') const dotenvPath = path.resolve(__dirname, '..', '.env') if (fs.existsSync(dotenvPath)) { require('dotenv').config({ path: dotenvPath }) } else { require('dotenv').config() } const hasConnectionString = typeof process.env.DATABASE_URL === 'string' && process.env.DATABASE_URL.trim().length > 0 if (!hasConnectionString) { console.warn('[db] DATABASE_URL is not set. Falling back to discrete Postgres environment variables.') } const sslConfig = process.env.DATABASE_SSL === 'true' || process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : undefined const defaultConfig = { host: process.env.PGHOST || 'localhost', port: Number(process.env.PGPORT || 5432), user: process.env.PGUSER || 'postgres', password: process.env.PGPASSWORD || 'postgres', database: process.env.PGDATABASE || 'claudia_blog' } const poolConfig = hasConnectionString ? { connectionString: process.env.DATABASE_URL.trim() } : { ...defaultConfig } if (sslConfig) { poolConfig.ssl = sslConfig } let pool let ensuringDatabasePromise function quoteIdentifier(identifier) { return `"${identifier.replace(/"/g, '""')}"` } function resolveTargetDatabase() { if (hasConnectionString) { try { const url = new URL(process.env.DATABASE_URL.trim()) const dbName = url.pathname ? url.pathname.replace(/^\//, '') : null return dbName || null } catch (error) { console.warn(`[db] Unable to parse DATABASE_URL: ${error.message}`) return null } } return poolConfig.database || null } async function ensureDatabaseExists(force = false) { if (ensuringDatabasePromise && !force) { return ensuringDatabasePromise } ensuringDatabasePromise = (async () => { const targetDb = resolveTargetDatabase() if (!targetDb) { return } let adminConfig if (hasConnectionString) { try { const adminUrl = new URL(process.env.DATABASE_URL.trim()) adminUrl.pathname = '/postgres' adminConfig = { connectionString: adminUrl.toString() } if (sslConfig) { adminConfig.ssl = sslConfig } } catch (error) { console.warn(`[db] Unable to prepare admin connection to create database: ${error.message}`) return } } else { const adminDatabase = process.env.PGDEFAULTDB || 'postgres' adminConfig = { ...poolConfig, database: adminDatabase } } const client = new Client(adminConfig) try { await client.connect() const exists = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [targetDb]) if (exists.rowCount === 0) { await client.query(`CREATE DATABASE ${quoteIdentifier(targetDb)}`) console.log(`[db] Created database ${targetDb}`) } } catch (error) { if (error.code === '42P04') { return } console.warn(`[db] Could not ensure database ${targetDb}: ${error.message}`) } finally { await client.end().catch(() => {}) } })() return ensuringDatabasePromise } async function initialisePool() { const activePool = new Pool(poolConfig) activePool.on('error', (err) => { console.error('[db] Unexpected error on idle client', err) }) // Force a connection so we surface errors immediately await activePool.query('SELECT 1') return activePool } async function getPool() { if (pool) { return pool } try { await ensureDatabaseExists() pool = await initialisePool() return pool } catch (error) { if (error.code === '3D000') { // Database missing, retry once after forcing ensure await ensureDatabaseExists(true) pool = await initialisePool() return pool } throw error } } async function query(text, params) { const activePool = await getPool() return activePool.query(text, params) } async function closePool() { if (pool) { await pool.end() pool = null } } module.exports = { query, getPool, closePool, ensureDatabaseExists }