From f741e88acbaf6135be575dc7f42b80bce735abad Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Sat, 30 May 2026 02:56:10 +0200 Subject: [PATCH] fix(database-backup): Postgres-safe insert destructure (runs the inline dump) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit databaseBackupService.backup() did `const [runId] = await db(...).insert({...})` without a .returning() — works on SQLite (knex returns [lastInsertId]) but throws "(intermediate value) is not iterable" on Postgres (knex returns a non-iterable shape). Bug was latent until Stage A of the backup-hardening plan wired this method into the "Run Backup Now" inline-dump path. Before Stage A only the scheduled cron + the dedicated admin-DB-backup page called it, and Ralf's install had never exercised either — so the inline-dump default landing in production was the first time the destructure ran on his PG. Cure: same explicit .returning('id') + dual-shape coalesce pattern that backupService.js uses for its own backup_runs insert (line 949). Two more sibling files have the same anti-pattern (userManagementService, customerAccountsService — invitation flows) and will bite under the same conditions; spawned a follow-up task to fix them in a separate PR. --- backend/src/services/databaseBackup.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/src/services/databaseBackup.js b/backend/src/services/databaseBackup.js index 1de8af4d..9911b317 100644 --- a/backend/src/services/databaseBackup.js +++ b/backend/src/services/databaseBackup.js @@ -309,8 +309,23 @@ class DatabaseBackupService { // Get current schema version const schemaVersion = await this.getCurrentSchemaVersion(); - // Create backup run record with version info - const [runId] = await db('database_backup_runs').insert({ + // Create backup run record with version info. + // + // Insert shape divergence between SQLite + Postgres made the old + // `const [runId] = await db(...).insert({...})` form throw + // "(intermediate value) is not iterable" on Postgres installs: + // + // - SQLite-via-knex: insert() returns `[lastInsertId]` (array) + // - Postgres-via-knex: insert() without .returning() returns an + // empty object / row count — not iterable + // + // Bug went undetected until Stage A wired this method into the + // "Run Backup Now" inline-dump path — before that, only the + // scheduled-cron + dedicated-admin-page callers exercised it, + // and Ralf's install had never triggered either. Cure: same + // explicit `.returning('id')` + dual-shape coalesce pattern that + // `backupService.js:949` uses for its own `backup_runs` insert. + const insertResult = await db('database_backup_runs').insert({ started_at: startTime, status: 'running', backup_type: this.dbType, @@ -324,8 +339,9 @@ class DatabaseBackupService { node_env: process.env.NODE_ENV || 'production', db_type: this.dbType }) - }); - + }).returning('id'); + const runId = insertResult[0]?.id || insertResult[0]; + backupRun = { id: runId }; // Get initial checksums