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