[object Object]

← back to Wallco Ai

unpublish endpoint: read+write the catalog snapshot (in-memory + data/designs.json) so it works on prod where PG is sparse. PG UPDATE retained as best-effort no-op on prod, source of truth locally.

a04f46cb870a7930fc80722d1256f45d29b8bbd8 · 2026-05-28 10:03:16 -0700 · Steve Abrams

Files touched

Diff

commit a04f46cb870a7930fc80722d1256f45d29b8bbd8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu May 28 10:03:16 2026 -0700

    unpublish endpoint: read+write the catalog snapshot (in-memory + data/designs.json) so it works on prod where PG is sparse. PG UPDATE retained as best-effort no-op on prod, source of truth locally.
---
 server.js | 47 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 36 insertions(+), 11 deletions(-)

diff --git a/server.js b/server.js
index b0a881f..783c03a 100644
--- a/server.js
+++ b/server.js
@@ -18451,20 +18451,45 @@ app.post('/api/design/:id/unpublish', requireAdmin, express.json({ limit: '1kb'
   const id = parseInt(req.params.id, 10);
   if (!Number.isFinite(id) || id < 1) return res.status(400).json({ error: 'bad id' });
   try {
-    const cur = psqlQuery(`SELECT is_published FROM all_designs WHERE id=${id};`);
-    if (!cur) return res.status(404).json({ error: 'design not found' });
-    const isLive = String(cur).trim() === 't';
+    // Catalog source of truth is the in-memory DESIGNS array (built from
+    // data/designs.json at boot). PG on prod is near-empty per CLAUDE.md, so
+    // reading is_published from PG would 404 on prod for any design not in
+    // that sparse table. The snapshot has every shipped row.
+    const d = DESIGNS.find(x => x.id === id);
+    if (!d) return res.status(404).json({ error: 'design not found in catalog snapshot' });
+    const isLive = d.is_published !== false;     // snapshot rows default to live
     const targetUnpub = (typeof req.body?.unpublish === 'boolean') ? req.body.unpublish : isLive;
     const next = targetUnpub ? false : true;
-    // all_designs has no updated_at column — keep the UPDATE minimal so the
-    // psqlExecLocal silent-on-error path doesn't swallow a real failure.
-    psqlExecLocal(`UPDATE all_designs SET is_published=${next ? 'TRUE' : 'FALSE'} WHERE id=${id};`);
-    // Bump in-memory snapshot too so subsequent renders agree
+    d.is_published = next;                        // 1) in-memory
+    // 2) PG UPDATE — works locally where PG mirrors the catalog; on prod the
+    //    row likely isn't there so this is a no-op, which is fine.
+    try { psqlExecLocal(`UPDATE all_designs SET is_published=${next ? 'TRUE' : 'FALSE'} WHERE id=${id};`); } catch {}
+    // 3) Patch data/designs.json on disk so the change survives pm2 reload.
+    //    Atomic write via tmp + rename so a crash mid-write can't corrupt the
+    //    snapshot. NOTE: the next Mac2 deploy will rsync over this file —
+    //    Steve should also flip it on Mac2 + refresh the snapshot if he wants
+    //    the change to persist across redeploys.
+    let snap_patched = false;
     try {
-      const d = DESIGNS.find(x => x.id === id);
-      if (d) d.is_published = next;
-    } catch {}
-    res.json({ ok: true, id, is_published: next });
+      const snapPath = path.join(__dirname, 'data', 'designs.json');
+      const snap = JSON.parse(fs.readFileSync(snapPath, 'utf8'));
+      const idx = snap.findIndex(x => x && x.id === id);
+      if (idx >= 0) {
+        snap[idx].is_published = next;
+        const tmp = snapPath + '.tmp-unpub-' + process.pid;
+        fs.writeFileSync(tmp, JSON.stringify(snap));
+        fs.renameSync(tmp, snapPath);
+        snap_patched = true;
+      }
+    } catch (e) {
+      console.warn('[unpublish] designs.json patch failed for #' + id + ':', e.message.slice(0, 120));
+    }
+    res.json({
+      ok: true, id, is_published: next, snap_patched,
+      note: snap_patched
+        ? 'Catalog snapshot patched. Next Mac2 deploy will overwrite unless you also flip it on Mac2.'
+        : 'In-memory only — snapshot patch failed; change resets on pm2 reload.'
+    });
   } catch (e) {
     res.status(500).json({ error: String(e.message || e).slice(0, 200) });
   }

← 82cafd8 cactus-curator: add ★ top-N rank pill + ✨ fresh chip (24h) t  ·  back to Wallco Ai  ·  etsyEligible: graceful fallback when seam-defect-boxes.py is f55c3ff →