Fix debug upload persistence and UX; exclude prod audit log from DB restore

Uploads and fixes now bust browser cache via file-mtime keys in API
responses. Debug upload shows a centered loading overlay and blocks search
while uploading. Prod DB restore skips curator_audit_log.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-09 18:30:19 +03:00
co-authored by Cursor
parent 62096e8210
commit f78c14f307
18 changed files with 506 additions and 154 deletions
+45 -6
View File
@@ -18,6 +18,23 @@ const { printCliResult } = require('./lib/cli-result');
const { Client } = pg;
// Prod restore keeps these tables untouched (no TRUNCATE, no INSERT from dev backup).
const PROD_RESTORE_SKIP_TABLES = new Set(['curator_audit_log']);
function getInsertTableName(statement) {
const match = statement.match(/^INSERT INTO "([^"]+)"/i)
|| statement.match(/^INSERT INTO ([a-zA-Z_][a-zA-Z0-9_]*)/i);
return match ? match[1] : null;
}
function filterInsertsForRestore(inserts, prod) {
if (!prod) return inserts;
return inserts.filter((statement) => {
const table = getInsertTableName(statement);
return !table || !PROD_RESTORE_SKIP_TABLES.has(table);
});
}
function parseArgs(argv) {
const fileIdx = argv.indexOf('--file');
if (fileIdx === -1 || !argv[fileIdx + 1]) {
@@ -71,14 +88,18 @@ function extractInsertStatements(content) {
return statements;
}
async function truncatePublicTables(client) {
async function truncatePublicTables(client, { excludeTables = new Set() } = {}) {
const tablesRes = await client.query(
`SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
ORDER BY table_name`,
);
const names = tablesRes.rows.map((r) => `"${r.table_name}"`).join(', ');
const names = tablesRes.rows
.map((r) => r.table_name)
.filter((name) => !excludeTables.has(name))
.map((name) => `"${name}"`)
.join(', ');
if (!names) return;
await client.query(`TRUNCATE TABLE ${names} RESTART IDENTITY CASCADE`);
}
@@ -92,14 +113,19 @@ async function main() {
const config = prod ? loadProdPgConfig() : loadDevPgConfig();
const dbName = config.database;
const inserts = extractInsertStatements(fs.readFileSync(filePath, 'utf8'));
const allInserts = extractInsertStatements(fs.readFileSync(filePath, 'utf8'));
const inserts = filterInsertsForRestore(allInserts, prod);
const skippedInserts = prod ? allInserts.length - inserts.length : 0;
if (inserts.length === 0) {
throw new Error('No INSERT statements found in backup file');
}
const skipNote = prod && PROD_RESTORE_SKIP_TABLES.size > 0
? `\nExcluded from prod restore: ${[...PROD_RESTORE_SKIP_TABLES].join(', ')}`
: '';
const prompt = prod
? `RESTORE ${inserts.length} rows into PRODUCTION "${dbName}" from:\n ${filePath}\nThis TRUNCATES all public tables first.`
? `RESTORE ${inserts.length} rows into PRODUCTION "${dbName}" from:\n ${filePath}\nThis TRUNCATES public tables first (except excluded tables).${skipNote}`
: `Restore ${inserts.length} rows into "${dbName}" from:\n ${filePath}\nThis TRUNCATES all public tables first.`;
if (prod) {
@@ -113,7 +139,12 @@ async function main() {
await client.connect();
console.log(`Truncating public tables in "${dbName}"...`);
await truncatePublicTables(client);
if (prod && PROD_RESTORE_SKIP_TABLES.size > 0) {
console.log(` skipping: ${[...PROD_RESTORE_SKIP_TABLES].join(', ')}`);
}
await truncatePublicTables(client, {
excludeTables: prod ? PROD_RESTORE_SKIP_TABLES : new Set(),
});
// Fast path: disable FK/trigger checks for a single-pass load. Requires
// permission to set session_replication_role (superuser, or on PG 15+ a
@@ -186,12 +217,20 @@ async function main() {
await client.end();
console.log(`Restore complete: ${restored} statements into "${dbName}".`);
if (skippedInserts > 0) {
console.log(`Skipped ${skippedInserts} INSERT statement(s) for excluded prod tables.`);
}
printCliResult({
script: 'restore-db-data',
ok: true,
summary: `Restore complete into "${dbName}".`,
details: [`Statements restored: ${restored}`],
details: [
`Statements restored: ${restored}`,
...(skippedInserts > 0
? [`Skipped (excluded tables): ${skippedInserts}`]
: []),
],
});
}