Files
wehub-resource-sync fed8b2eed7
Backend release / release (push) Waiting to run
Bandit Security Scan / bandit_scan (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push multi-arch DocsGPT Docker image / manifest (push) Blocked by required conditions
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Waiting to run
Build and push DocsGPT FE Docker image for development / manifest (push) Blocked by required conditions
Python linting / ruff (push) Waiting to run
Run python tests with pytest / Run tests and count coverage (3.12) (push) Waiting to run
React Widget Build / build (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:28:29 +08:00

87 lines
2.5 KiB
TypeScript

/**
* Thin pg wrapper + typed row helpers for DB assertions in specs.
*/
import pgPkg from 'pg';
const { Pool } = pgPkg;
const DEFAULT_URI =
process.env.POSTGRES_URI ??
'postgresql://docsgpt:docsgpt@127.0.0.1:5432/docsgpt_e2e';
// Cap at 2: specs run serially (workers: 1) so one live + one spare is plenty,
// and we must not starve Flask's own pool on the shared Postgres instance.
const pool = new Pool({ connectionString: DEFAULT_URI, max: 2 });
/**
* Thin wrapper over the pg pool. Always parameterized — callers pass `$1, $2, …`
* in `sql` and the values in `params`. No string interpolation anywhere.
*/
export const pg = {
async query<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<{ rows: T[] }> {
const res = params === undefined
? await pool.query(sql)
: await pool.query(sql, params as unknown[]);
return { rows: res.rows as T[] };
},
async close(): Promise<void> {
await pool.end();
},
};
const SAFE_IDENT = /^[a-z_][a-z_0-9]*$/;
function assertSafeTable(table: string): void {
if (!SAFE_IDENT.test(table)) {
throw new Error(
`countRows: refusing unsafe table identifier ${JSON.stringify(table)}`,
);
}
}
/**
* Return the row count for a table, optionally filtered by a parameterized
* WHERE clause (caller supplies `sql` like `"user_id = $1"` and matching
* `params`). `table` is whitelisted against `/^[a-z_][a-z_0-9]*$/`.
*/
export async function countRows(
table: string,
where?: { sql: string; params?: unknown[] },
): Promise<number> {
assertSafeTable(table);
const whereClause = where?.sql ? ` WHERE ${where.sql}` : '';
const { rows } = await pg.query<{ n: string }>(
`SELECT count(*)::text AS n FROM ${table}${whereClause}`,
where?.params,
);
return Number(rows[0]?.n ?? 0);
}
/**
* Shape of a row in `public.users` (Alembic 0001_initial). `agent_preferences`
* is JSONB — pg returns it as a parsed JS value.
*/
export interface UserRow {
id: string;
user_id: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
agent_preferences: any;
}
/**
* Look up a user row by the external `user_id` (JWT `sub`). Returns null if
* `ensure_user_doc` hasn't fired yet — useful for asserting first-touch upsert
* behavior.
*/
export async function getUserRow(userId: string): Promise<UserRow | null> {
const { rows } = await pg.query<UserRow>(
'SELECT id, user_id, agent_preferences FROM users WHERE user_id = $1',
[userId],
);
return rows[0] ?? null;
}