← back to Interiordesignershowroom
add scripts/fix-dangling-scenes.js — NULL out dangling scene_image DB refs on prod (TK-10390)
0acaddb1cd9aba35a5aa98074342d0aeb73674ec · 2026-08-10 04:12:32 -0700 · steve@designerwallcoverings.com
Files touched
A scripts/fix-dangling-scenes.js
Diff
commit 0acaddb1cd9aba35a5aa98074342d0aeb73674ec
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Mon Aug 10 04:12:32 2026 -0700
add scripts/fix-dangling-scenes.js — NULL out dangling scene_image DB refs on prod (TK-10390)
---
scripts/fix-dangling-scenes.js | 70 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/scripts/fix-dangling-scenes.js b/scripts/fix-dangling-scenes.js
new file mode 100644
index 0000000..733e699
--- /dev/null
+++ b/scripts/fix-dangling-scenes.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+// Fix dangling scene_image references — sets scene_image=NULL for rows where
+// the file no longer exists on disk. Safe: the server already gracefully falls
+// back to product thumbs when scene_image is NULL.
+// Run locally: node ids-fix-dangling-scenes.js [--apply]
+// Run on prod: SSH to Kamatera, copy this file, run with --apply
+
+const { Pool } = require('pg');
+const fs = require('fs');
+const path = require('path');
+
+const APPLY = process.argv.includes('--apply');
+const PUBLIC_DIR = path.join(__dirname, '..', 'public'); // on prod: /root/Projects/interiordesignershowroom/public
+
+const pool = new Pool({
+ connectionString: process.env.DATABASE_URL || 'postgresql://localhost:5432/idshowroom',
+});
+
+async function main() {
+ const client = await pool.connect();
+ try {
+ const { rows } = await client.query(
+ `SELECT id, slug, scene_image FROM rooms WHERE scene_image IS NOT NULL ORDER BY id`
+ );
+ console.log(`Found ${rows.length} rooms with scene_image set.`);
+
+ const dangling = [];
+ for (const row of rows) {
+ const imgPath = path.join(PUBLIC_DIR, row.scene_image);
+ if (!fs.existsSync(imgPath)) {
+ dangling.push(row);
+ console.log(`MISSING: /room/${row.slug} → ${row.scene_image}`);
+ } else {
+ console.log(`OK: /room/${row.slug} → ${row.scene_image}`);
+ }
+ }
+
+ console.log(`\nTotal dangling: ${dangling.length}/${rows.length}`);
+
+ if (dangling.length === 0) {
+ console.log('Nothing to fix — all scene_image files exist.');
+ return;
+ }
+
+ if (!APPLY) {
+ console.log('\nDRY RUN — pass --apply to NULL out dangling references.');
+ console.log('Nulling these would let the server fall back to product thumbs (graceful-degrade).');
+ return;
+ }
+
+ await client.query('BEGIN');
+ const ids = dangling.map(r => r.id);
+ const { rowCount } = await client.query(
+ `UPDATE rooms SET scene_image = NULL WHERE id = ANY($1::int[])`,
+ [ids]
+ );
+ await client.query('COMMIT');
+ console.log(`\nApplied: NULLed scene_image on ${rowCount} rows.`);
+ console.log('These rooms now show product thumbs as their hero (graceful-degrade is live).');
+ } catch (e) {
+ await client.query('ROLLBACK').catch(() => {});
+ console.error('Error:', e.message);
+ process.exit(1);
+ } finally {
+ client.release();
+ await pool.end();
+ }
+}
+
+main();
← 9530ee0 auto-data-snapshot: 2026-08-09T17:28:38 (2 data files) — .pl
·
back to Interiordesignershowroom
·
TK-10390: reconcile-dead-images.js — dry-run tool to NULL de c6f5c6b →