← back to Ca Donations
lib/db.js
58 lines
// Shared pg pool — local dw-style Postgres on the /tmp socket.
import pg from 'pg';
export const pool = new pg.Pool({
host: process.env.PGHOST || '/tmp',
database: process.env.PGDATABASE || 'ca_donations',
max: 8,
});
export async function q(text, params) {
const res = await pool.query(text, params);
return res.rows;
}
// Run a query with a bounded statement_timeout on a dedicated client, so a heavy
// aggregation (e.g. the public political rollup over the full table) cancels
// cleanly instead of tying up a connection. The timeout is session-local and the
// client is reset before returning to the pool.
export async function qWithTimeout(text, params, ms = 8000) {
const client = await pool.connect();
try {
await client.query(`SET statement_timeout = ${Number(ms)}`);
const res = await client.query(text, params);
return res.rows;
} finally {
try { await client.query('SET statement_timeout = 0'); } catch (_) {}
client.release();
}
}
// Begin/finish an ingest_runs audit row. Returns { id, done(rowsIn, rowsUpsert, status, detail) }.
export async function startRun(sourceSlug) {
const [row] = await q(
`INSERT INTO ingest_runs (source_slug) VALUES ($1) RETURNING id`,
[sourceSlug],
);
return {
id: row.id,
async done(rowsIn, rowsUpsert, status = 'ok', detail = null) {
await q(
`UPDATE ingest_runs SET finished_at=now(), rows_in=$2, rows_upsert=$3, status=$4, detail=$5 WHERE id=$1`,
[row.id, rowsIn, rowsUpsert, status, detail],
);
},
};
}
export async function upsertSource(s) {
await q(
`INSERT INTO sources (slug,name,url,jurisdiction,family,granularity,access,notes)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
ON CONFLICT (slug) DO UPDATE SET name=EXCLUDED.name, url=EXCLUDED.url,
jurisdiction=EXCLUDED.jurisdiction, family=EXCLUDED.family,
granularity=EXCLUDED.granularity, access=EXCLUDED.access, notes=EXCLUDED.notes`,
[s.slug, s.name, s.url, s.jurisdiction, s.family, s.granularity, s.access, s.notes],
);
}