From 23e1210e86967cb1af3316479831bbee9f9934fa Mon Sep 17 00:00:00 2001 From: Danila Khodjaef Date: Mon, 27 Jul 2026 20:14:51 +0300 Subject: [PATCH] Release: weekly deploy --- scripts/backup-db-data.js | 5 +++++ scripts/restore-db-data.js | 26 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/scripts/backup-db-data.js b/scripts/backup-db-data.js index 4903334..9c37edc 100644 --- a/scripts/backup-db-data.js +++ b/scripts/backup-db-data.js @@ -32,6 +32,11 @@ function sqlLiteral(value) { if (typeof value === 'boolean') return value ? 'TRUE' : 'FALSE'; if (value instanceof Date) return `'${value.toISOString()}'`; if (typeof value === 'number' || typeof value === 'bigint') return String(value); + // node-pg returns JS arrays for Postgres array columns (e.g. users.permissions TEXT[]) + if (Array.isArray(value)) { + if (value.length === 0) return `ARRAY[]::text[]`; + return `ARRAY[${value.map((item) => sqlLiteral(item)).join(', ')}]`; + } if (typeof value === 'object') { return `'${JSON.stringify(value).replace(/'/g, "''")}'`; } diff --git a/scripts/restore-db-data.js b/scripts/restore-db-data.js index d9e8c16..ec1d437 100644 --- a/scripts/restore-db-data.js +++ b/scripts/restore-db-data.js @@ -27,6 +27,28 @@ function getInsertTableName(statement) { return match ? match[1] : null; } +/** + * Older backups serialized TEXT[] (e.g. users.permissions) via JSON.stringify, + * producing '["a","b"]' which Postgres rejects as an array literal. Rewrite + * JSON string-array literals to ARRAY['a','b']::text[]. + */ +function coerceJsonStringArrayLiterals(statement) { + return statement.replace(/'(\[(?:"(?:\\.|[^"\\])*"(?:\s*,\s*"(?:\\.|[^"\\])*")*)?\])'/g, (full, jsonBody) => { + let arr; + try { + arr = JSON.parse(jsonBody); + } catch { + return full; + } + if (!Array.isArray(arr) || !arr.every((item) => typeof item === 'string')) { + return full; + } + if (arr.length === 0) return `ARRAY[]::text[]`; + const items = arr.map((s) => `'${String(s).replace(/'/g, "''")}'`).join(', '); + return `ARRAY[${items}]::text[]`; + }); +} + function filterInsertsForRestore(inserts, prod) { if (!prod) return inserts; return inserts.filter((statement) => { @@ -113,7 +135,9 @@ async function main() { const config = prod ? loadProdPgConfig() : loadDevPgConfig(); const dbName = config.database; - const allInserts = extractInsertStatements(fs.readFileSync(filePath, 'utf8')); + const allInserts = extractInsertStatements(fs.readFileSync(filePath, 'utf8')).map( + coerceJsonStringArrayLiterals + ); const inserts = filterInsertsForRestore(allInserts, prod); const skippedInserts = prod ? allInserts.length - inserts.length : 0;