Scaffold dd0c/drift SaaS backend: Fastify, RLS, ingestion, dashboard API
- Fastify server with Zod validation, pino logging, CORS/helmet - Drift report ingestion endpoint with nonce replay prevention - Dashboard API: stacks list, drift history, report detail, summary stats - PostgreSQL schema with RLS: tenants, users, agent_keys, drift_reports, remediation_actions - withTenant() helper for safe connection pool tenant context management - Config via Zod-validated env vars
This commit is contained in:
51
products/02-iac-drift-detection/saas/src/data/db.ts
Normal file
51
products/02-iac-drift-detection/saas/src/data/db.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import pg from 'pg';
|
||||
|
||||
export function createPool(connectionString: string): pg.Pool {
|
||||
return new pg.Pool({
|
||||
connectionString,
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tenant context on a pooled connection for RLS.
|
||||
* MUST be called before any query in a multi-tenant context.
|
||||
* MUST be cleared when returning the connection to the pool.
|
||||
*/
|
||||
export async function setTenantContext(client: pg.PoolClient, tenantId: string): Promise<void> {
|
||||
await client.query('SET LOCAL app.tenant_id = $1', [tenantId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear tenant context — call this in a finally block before releasing the client.
|
||||
*/
|
||||
export async function clearTenantContext(client: pg.PoolClient): Promise<void> {
|
||||
await client.query('RESET app.tenant_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query within a tenant-scoped transaction.
|
||||
* Handles SET LOCAL + RESET automatically to prevent RLS leakage via connection pooling.
|
||||
*/
|
||||
export async function withTenant<T>(
|
||||
pool: pg.Pool,
|
||||
tenantId: string,
|
||||
fn: (client: pg.PoolClient) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
await setTenantContext(client, tenantId);
|
||||
const result = await fn(client);
|
||||
await client.query('COMMIT');
|
||||
return result;
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK');
|
||||
throw err;
|
||||
} finally {
|
||||
await clearTenantContext(client);
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
10
products/02-iac-drift-detection/saas/src/data/redis.ts
Normal file
10
products/02-iac-drift-detection/saas/src/data/redis.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
export function createRedis(url: string): Redis {
|
||||
return new Redis(url, {
|
||||
maxRetriesPerRequest: 3,
|
||||
retryStrategy(times) {
|
||||
return Math.min(times * 200, 3000);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user