← 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 →