[object Object]

← back to Wallco Ai

feat(colorways): admin user fallback for save/list/delete

18b423ba358dada03dd824f1dffe77e13da7b452 · 2026-05-29 14:31:21 -0700 · Steve Abrams

Steve 2026-05-29 'test it as admin user'. Admin auth (loopback / JWT
cookie / ?admin=<token>) now satisfies the trade-user gate by routing
to a synthetic wallco_trade_users row ('admin-system@wallco.ai',
created lazily on first admin use, id cached in process). All admins
share this row — fine since Steve is typically the sole admin.

Files touched

Diff

commit 18b423ba358dada03dd824f1dffe77e13da7b452
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri May 29 14:31:21 2026 -0700

    feat(colorways): admin user fallback for save/list/delete
    
    Steve 2026-05-29 'test it as admin user'. Admin auth (loopback / JWT
    cookie / ?admin=<token>) now satisfies the trade-user gate by routing
    to a synthetic wallco_trade_users row ('admin-system@wallco.ai',
    created lazily on first admin use, id cached in process). All admins
    share this row — fine since Steve is typically the sole admin.
---
 src/colorways.js | 43 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 40 insertions(+), 3 deletions(-)

diff --git a/src/colorways.js b/src/colorways.js
index 2f27789..733885e 100644
--- a/src/colorways.js
+++ b/src/colorways.js
@@ -24,12 +24,49 @@
 const path = require('path');
 const fs = require('fs');
 const { psqlQuery: psql, pgEsc } = require('../lib/db');
+const { isAdmin } = require('./admin-gate');
 const expressJson = require('express').json({ limit: '32mb' });   // raised — data_url PNGs
 
+// Synthetic admin trade-user — lets logged-in admins (or loopback) save
+// colorways without having a real trade-user account. Created lazily on first
+// admin use. Shared across all admin sessions (Steve is typically the only
+// admin). Email is sentinel-shaped so it can never collide with a real user.
+const ADMIN_USER_EMAIL = 'admin-system@wallco.ai';
+let _adminUserIdCache = null;
+function getOrCreateAdminUserId() {
+  if (_adminUserIdCache) return _adminUserIdCache;
+  try {
+    let row = psql(`SELECT id FROM wallco_trade_users WHERE email=${pgEsc(ADMIN_USER_EMAIL)} LIMIT 1;`).trim();
+    if (!row) {
+      row = psql(`INSERT INTO wallco_trade_users (email, name)
+                  VALUES (${pgEsc(ADMIN_USER_EMAIL)}, 'Admin (system)')
+                  RETURNING id;`).trim();
+    }
+    _adminUserIdCache = parseInt(row, 10) || null;
+    return _adminUserIdCache;
+  } catch (e) {
+    console.error('[colorways] getOrCreateAdminUserId failed:', e.message);
+    return null;
+  }
+}
+
 function mount(app, deps) {
   const { getTradeUser } = deps || {};
   if (!getTradeUser) { console.error('[colorways] missing getTradeUser dep — not mounted'); return; }
 
+  // Effective user resolver: real trade-user takes priority; admin (loopback,
+  // JWT cookie, or ?admin=<token>) falls back to the synthetic admin user.
+  // Steve 2026-05-29: "test it as admin user" — admins now save colorways too.
+  function getEffectiveUser(req) {
+    const tu = getTradeUser(req);
+    if (tu) return tu;
+    if (isAdmin(req)) {
+      const id = getOrCreateAdminUserId();
+      if (id) return { id, email: ADMIN_USER_EMAIL, name: 'Admin', is_admin: true };
+    }
+    return null;
+  }
+
   // ── migration (idempotent) ──────────────────────────────────────────────
   try {
     psql(`CREATE TABLE IF NOT EXISTS wallco_user_colorways (
@@ -54,7 +91,7 @@ function mount(app, deps) {
 
   // POST /api/colorways/save
   app.post('/api/colorways/save', expressJson, (req, res) => {
-    const u = getTradeUser(req);
+    const u = getEffectiveUser(req);
     if (!u) return res.status(401).json({ ok: false, error: 'Sign in to save your exclusive colorway.' });
     const b = req.body || {};
     const designId = parseInt(b.design_id, 10);
@@ -161,7 +198,7 @@ RETURNING id;`).trim();
 
   // GET /api/colorways?design_id=N — current user's exclusive colorways for a design.
   app.get('/api/colorways', (req, res) => {
-    const u = getTradeUser(req);
+    const u = getEffectiveUser(req);
     if (!u) return res.json({ ok: true, authenticated: false, colorways: [] });
     const designId = parseInt(req.query.design_id, 10);
     if (!Number.isFinite(designId) || designId <= 0) return res.status(400).json({ ok: false, error: 'design_id required.' });
@@ -184,7 +221,7 @@ RETURNING id;`).trim();
 
   // DELETE /api/colorways/:id — remove one of the user's own colorways.
   app.delete('/api/colorways/:id', (req, res) => {
-    const u = getTradeUser(req);
+    const u = getEffectiveUser(req);
     if (!u) return res.status(401).json({ ok: false, error: 'Sign in.' });
     const id = parseInt(req.params.id, 10);
     if (!Number.isFinite(id)) return res.status(400).json({ ok: false, error: 'bad id' });

← b6425eb feat(colorways): promote saved colorway to a real spoon_all_  ·  back to Wallco Ai  ·  admin/rooms: img src prefers /designs/room/design_<id>_<type 6a83e70 →