← back to PoppyPetitions
lib/db.ts
75 lines
import { Pool, PoolClient, QueryResult, QueryResultRow } from 'pg';
// 2026-05-05 (P0 leak scrub): hardcoded DB-password fallback removed.
// Also: the fallback pointed at dw_unified (DW commerce DB) which is the
// wrong database — PoppyPetitions uses its own schema. Fail-fast on missing
// DATABASE_URL is safer than silently writing to the wrong place.
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL env var is required (no fallback)');
}
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
// Log pool errors so they don't crash the process
pool.on('error', (err: Error) => {
console.error('[db] Unexpected pool error:', err.message);
});
/**
* Execute a parameterized query against the pool.
* Automatically sets the search_path to the poppy schema.
* Returns the full QueryResult so callers can read .rows, .rowCount, etc.
*/
export async function query<T extends QueryResultRow = QueryResultRow>(
text: string,
params?: unknown[],
): Promise<QueryResult<T>> {
const start = Date.now();
const client = await pool.connect();
try {
// Set schema search path for this session
await client.query('SET search_path TO poppy, public');
const result = await client.query<T>(text, params);
const duration = Date.now() - start;
if (duration > 2000) {
console.warn(`[db] Slow query (${duration}ms):`, text.slice(0, 120));
}
return result;
} catch (err) {
console.error('[db] Query error:', (err as Error).message, '\n SQL:', text.slice(0, 200));
throw err;
} finally {
client.release();
}
}
/**
* Acquire a dedicated client from the pool — use for transactions.
* IMPORTANT: Always call client.release() in a finally block.
* Remember to SET search_path TO poppy, public before your queries.
*
* Usage:
* const client = await getClient();
* try {
* await client.query('SET search_path TO poppy, public');
* await client.query('BEGIN');
* // ... transactional work ...
* await client.query('COMMIT');
* } catch (err) {
* await client.query('ROLLBACK');
* throw err;
* } finally {
* client.release();
* }
*/
export async function getClient(): Promise<PoolClient> {
return pool.connect();
}
export default pool;