Files
pn-new-crm/scripts/db-reset.ts
Matt Ciaccio d2171ea79b feat(audit): comprehensive logging — auth events, severity, source, IP
Audit log was previously silent on authentication and on background
work. This wires:

- Login (success + failed) and logout via a wrapper around better-auth's
  [...all] handler. Failed logins are severity 'warning' and carry the
  attempted email so brute-force attempts surface in the inspector.
- New severity (info|warning|error|critical) and source (user|auth|
  system|webhook|cron|job) columns on audit_logs. permission_denied
  defaults to 'warning', hard_delete to 'critical'.
- Webhook delivery success/failure/DLQ/retry now write audit rows
  alongside the webhook_deliveries detail table.
- IP address is now visible as a column in the inspector (was already
  captured at the helper level).
- Audit UI: severity badges per row, severity + source dropdowns, IP
  column, expanded action filter covering hard-delete, webhook events,
  job/cron events.

Migration 0044 adds the two columns + their port-scoped indexes.
1175/1175 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 20:35:34 +02:00

98 lines
3.0 KiB
TypeScript

/**
* Wipe all data from the database, preserving schema + drizzle migration
* history. Run before swapping seed fixtures.
*
* pnpm tsx scripts/db-reset.ts (refuses without --confirm)
* pnpm tsx scripts/db-reset.ts --confirm
*
* Truncates every table in the `public` schema except the drizzle
* migration tracker, then resets sequences. Wraps the loop in a single
* transaction so a mid-wipe failure rolls back cleanly.
*
* Refuses to run when DATABASE_URL points at anything that doesn't look
* like a local/dev host. Override with --i-know-what-im-doing.
*/
import 'dotenv/config';
import postgres from 'postgres';
const url: string = process.env.DATABASE_URL ?? '';
if (!url) {
console.error('DATABASE_URL is not set; aborting.');
process.exit(1);
}
const args = new Set(process.argv.slice(2));
if (!args.has('--confirm')) {
console.error('Refusing to wipe without --confirm');
console.error('Run again as: pnpm tsx scripts/db-reset.ts --confirm');
process.exit(1);
}
// Best-effort safety: refuse for anything that doesn't look like a local DB.
function looksLocal(u: string): boolean {
try {
const parsed = new URL(u);
return (
parsed.hostname === 'localhost' ||
parsed.hostname === '127.0.0.1' ||
parsed.hostname === '::1' ||
parsed.hostname.endsWith('.local') ||
parsed.hostname.endsWith('.internal') ||
parsed.hostname === 'host.docker.internal' ||
// Docker compose service names commonly used here
parsed.hostname === 'postgres' ||
parsed.hostname === 'db'
);
} catch {
return false;
}
}
if (!looksLocal(url) && !args.has('--i-know-what-im-doing')) {
console.error(
`DATABASE_URL host doesn't look local. Refusing to wipe a remote DB without --i-know-what-im-doing.`,
);
process.exit(1);
}
const sql = postgres(url, { max: 1 });
async function main() {
console.log('Resetting database...');
console.log(` url: ${url.replace(/:[^:@]*@/, ':***@')}`);
const tables = await sql<{ tablename: string }[]>`
SELECT tablename FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT LIKE 'drizzle_%'
AND tablename != '__drizzle_migrations'
`;
if (tables.length === 0) {
console.log(' no user tables found, nothing to do.');
await sql.end();
return;
}
// Single TRUNCATE … CASCADE is faster than per-table loops and handles
// FK ordering for us. Quote table names defensively.
const tableList = tables.map((t) => `"public"."${t.tablename}"`).join(', ');
console.log(` truncating ${tables.length} tables...`);
await sql.unsafe(`TRUNCATE ${tableList} RESTART IDENTITY CASCADE`);
console.log(' done.');
await sql.end();
console.log('');
console.log('Database reset complete. Run a seed script next:');
console.log(' pnpm db:seed # realistic NocoDB-shaped fixture');
console.log(' pnpm db:seed:synthetic # one client per pipeline stage');
}
main().catch(async (err) => {
console.error('Reset failed:', err);
await sql.end().catch(() => undefined);
process.exit(1);
});