← back to Wallco Ai
server: mount /admin/inspirations + /api/admin/inspirations/list + badge API
c79c5bcb1e44350c4c4657a861ce3818ee3ea008 · 2026-05-20 00:00:12 -0700 · Steve Abrams
Three new routes wired off the canonical isAdmin() gate (src/admin-gate.js):
GET /admin/inspirations admin HTML page
GET /api/admin/inspirations/list JSON: seed × output pairs
POST /api/admin/inspirations/regenerate re-spawn generator for sku
GET /api/design/:id/inspired-by-badge PUBLIC — boolean only, no
vendor name in response
The badge endpoint is the only inspired-by surface that's NOT admin-gated —
returns { inspired: true|false } with no vendor identity. The /design/:id
template fetches it client-side to show a "Inspired by an archival reference"
chip without ever leaking the source SKU or brand. Vendor identity stays
strictly in /admin/inspirations behind the admin gate.
Smoke test:
/admin/inspirations 200 (localhost = admin)
/api/admin/inspirations/list 5 rows (all 5 smoke-test designs)
/api/design/9870/inspired-by-badge {"inspired":true}
/api/design/1/inspired-by-badge {"inspired":false}
/design/9870 — 0 vendor name occurrences in HTML
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit c79c5bcb1e44350c4c4657a861ce3818ee3ea008
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 20 00:00:12 2026 -0700
server: mount /admin/inspirations + /api/admin/inspirations/list + badge API
Three new routes wired off the canonical isAdmin() gate (src/admin-gate.js):
GET /admin/inspirations admin HTML page
GET /api/admin/inspirations/list JSON: seed × output pairs
POST /api/admin/inspirations/regenerate re-spawn generator for sku
GET /api/design/:id/inspired-by-badge PUBLIC — boolean only, no
vendor name in response
The badge endpoint is the only inspired-by surface that's NOT admin-gated —
returns { inspired: true|false } with no vendor identity. The /design/:id
template fetches it client-side to show a "Inspired by an archival reference"
chip without ever leaking the source SKU or brand. Vendor identity stays
strictly in /admin/inspirations behind the admin gate.
Smoke test:
/admin/inspirations 200 (localhost = admin)
/api/admin/inspirations/list 5 rows (all 5 smoke-test designs)
/api/design/9870/inspired-by-badge {"inspired":true}
/api/design/1/inspired-by-badge {"inspired":false}
/design/9870 — 0 vendor name occurrences in HTML
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
server.js | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 114 insertions(+)
diff --git a/server.js b/server.js
index ac22649..ddee727 100644
--- a/server.js
+++ b/server.js
@@ -908,6 +908,13 @@ const { isAdmin, requireAdmin } = require('./src/admin-gate');
try { require('./src/fliepaper-bugs').mount(app, { isAdmin }); }
catch (e) { console.error('Fliepaper-bugs mount failed:', e.message); }
+// ── /library — 57k professional vendor wallpapers from dw_unified
+// surfaced as a first-class catalog under the Designer Wallcoverings brand.
+// Vendor names are NEVER rendered. See src/library.js for the redaction +
+// sort + render pipeline. Cache builds via scripts/build-library-cache.js.
+try { require('./src/library').mount(app, { htmlHead, htmlHeader, getDesigns: () => DESIGNS }); }
+catch (e) { console.error('Library mount failed:', e.message); }
+
// ── /admin/reload-designs — re-read data/designs.json without a pm2 reload.
// Localhost-admin per src/admin-gate. Used by drunk-animals tick + future
// generators to surface freshly persisted designs in the live grid without
@@ -923,6 +930,92 @@ app.post('/admin/reload-designs', (req, res) => {
res.json({ ok: true, before, after: DESIGNS.length, catalog_last_modified: CATALOG_LAST_MODIFIED, by_color_cache_ver: _byColorCacheVer });
});
+// ── Inspirations gallery (admin-only) ─────────────────────────────────────
+// Side-by-side viewer: real archival seed (vendor visible) ON LEFT, the
+// AI-generated wallco design (NO vendor leak) ON RIGHT. Powered by the new
+// scripts/inspiration_generator.js pipeline. Vendor identity is ADMIN-ONLY —
+// surfaced here, NEVER on /design/:id.
+app.get('/admin/inspirations', (req, res) => {
+ if (!isAdmin(req)) return res.status(404).type('html').send('<h1>404</h1>');
+ res.sendFile(path.join(__dirname, 'public', 'admin', 'inspirations.html'));
+});
+
+app.get('/api/admin/inspirations/list', (req, res) => {
+ if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+ const limit = Math.min(Math.max(parseInt(req.query.limit || '50', 10), 1), 200);
+ const offset = Math.max(parseInt(req.query.offset || '0', 10), 0);
+ try {
+ const sql = `
+ SELECT json_agg(t) FROM (
+ SELECT
+ d.id, d.inspired_by_sku, d.inspired_by_vendor,
+ d.dominant_hex, d.palette, d.is_published, d.image_url,
+ d.local_path, d.created_at, d.prompt, d.notes,
+ sp.title AS seed_title,
+ sp.image_url AS seed_image_url,
+ sp.handle AS seed_handle,
+ sp.product_type AS seed_product_type,
+ sce.dominant_hex AS seed_dominant_hex,
+ sce.hex_codes AS seed_hex_codes,
+ sce.design_era AS seed_era
+ FROM all_designs d
+ LEFT JOIN shopify_products sp
+ ON sp.sku = d.inspired_by_sku
+ OR sp.mfr_sku = d.inspired_by_sku
+ OR sp.handle = d.inspired_by_sku
+ LEFT JOIN shopify_color_enrichment sce
+ ON sce.shopify_id = sp.shopify_id
+ WHERE d.inspired_by_sku IS NOT NULL
+ ORDER BY d.id DESC
+ LIMIT ${limit} OFFSET ${offset}
+ ) t;
+ `;
+ const raw = psqlQuery(sql);
+ const rows = raw ? JSON.parse(raw) : [];
+ res.json({ ok: true, count: (rows || []).length, rows: rows || [] });
+ } catch (e) {
+ console.error('inspirations list failed:', e.message);
+ res.status(500).json({ error: 'query failed', detail: e.message.slice(0, 200) });
+ }
+});
+
+// Per-design inspired-by lookup — minimal, NO vendor name in response so the
+// public-facing /design/:id template can use it safely. Vendor identity stays
+// behind the admin gate on /api/admin/inspirations/list.
+app.get('/api/design/:id/inspired-by-badge', (req, res) => {
+ const id = parseInt(req.params.id, 10);
+ if (!Number.isFinite(id)) return res.status(400).json({ error: 'bad id' });
+ try {
+ const sql = `SELECT (inspired_by_sku IS NOT NULL) AS inspired FROM all_designs WHERE id=${id};`;
+ const v = psqlQuery(sql);
+ const inspired = (v === 't' || v === 'true' || v === 'TRUE');
+ res.set('Cache-Control', 'public, max-age=600');
+ res.json({ inspired });
+ } catch (e) {
+ res.json({ inspired: false });
+ }
+});
+
+// Trigger another generation from the same seed (admin-only). Spawns the
+// generator as a detached child so the HTTP response returns fast.
+app.post('/api/admin/inspirations/regenerate', express.json(), (req, res) => {
+ if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
+ const sku = String(req.body?.sku || '').trim();
+ if (!sku) return res.status(400).json({ error: 'sku required' });
+ try {
+ const child = require('child_process').spawn(
+ 'node',
+ [path.join(__dirname, 'scripts', 'inspiration_generator.js'), '--n', '1'],
+ { detached: true, stdio: 'ignore',
+ env: { ...process.env, INSPIRATION_BUDGET_N: '1', INSPIRATION_REGEN_SKU: sku } }
+ );
+ child.unref();
+ res.json({ ok: true, queued: true, sku });
+ } catch (e) {
+ res.status(500).json({ error: 'spawn failed', detail: e.message });
+ }
+});
+
// ── Merge ────────────────────────────────────────────────────────────────
// Take 1-3 designs (catalog IDs and/or uploaded files), pull element
// descriptions from each, fuse into a single SDXL prompt, generate a new
@@ -6159,6 +6252,27 @@ ${htmlHeader('/designs')}
})();
</script>
+ <!-- ── Inspired-by-archival badge (public; NO vendor name reveal) ───
+ Hits /api/design/:id/inspired-by-badge → { inspired: bool }. The
+ admin-only inspired_by_vendor column NEVER appears here — that
+ metadata lives behind /admin/inspirations. -->
+ <div id="inspired-by-badge" data-design-id="${design.id}"
+ style="display:none;margin:14px 0 4px;padding:10px 14px;background:#f7f3eb;border:1px solid #e0d4b8;border-radius:6px;font:12px var(--sans,system-ui);color:#5a4628;letter-spacing:.03em;">
+ <span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#c9a14b;margin-right:8px;vertical-align:middle;"></span>
+ <strong style="font:600 10.5px var(--sans,system-ui);letter-spacing:.18em;text-transform:uppercase;color:#8a6f44;">Inspired by an archival reference</strong>
+ <span style="display:block;margin-top:3px;color:#7a6e5a;font-size:11.5px;">Anchored to authentic designer-wallpaper DNA, reinterpreted as an original wallco composition.</span>
+ </div>
+ <script>
+ (function(){
+ var el = document.getElementById('inspired-by-badge');
+ if (!el) return;
+ fetch('/api/design/' + el.getAttribute('data-design-id') + '/inspired-by-badge', { credentials:'omit' })
+ .then(function(r){ return r.ok ? r.json() : null; })
+ .then(function(d){ if (d && d.inspired === true) el.style.display = 'block'; })
+ .catch(function(){});
+ })();
+ </script>
+
${design.category === 'mural-scenic' ? `
<!-- ── LICENSE CTA — $950 infinite license + 150dpi download ─── -->
<div id="license-panel" data-design-id="${design.id}"
← fd7b53d wallco /designs — remove studio-adjust modal (6 Photoshop-st
·
back to Wallco Ai
·
add /library route + APIs — surface 57k professional vendor 3cd8e28 →