[object Object]

← back to Dw Internal Gateway

every vendor follows all.dw schema: vendor-locked proxy to local all.dw (live), catalog fallback, archived-lifecycle view for fully-archived lines — 362 vendor sites, zero 404s

e9fe407b232781af9ee67ef5ec187c7db05dc329 · 2026-07-23 11:46:34 -0700 · Steve Abrams

Files touched

Diff

commit e9fe407b232781af9ee67ef5ec187c7db05dc329
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 23 11:46:34 2026 -0700

    every vendor follows all.dw schema: vendor-locked proxy to local all.dw (live), catalog fallback, archived-lifecycle view for fully-archived lines — 362 vendor sites, zero 404s
---
 server.js | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 145 insertions(+), 19 deletions(-)

diff --git a/server.js b/server.js
index 453f8f6..62b3bcf 100644
--- a/server.js
+++ b/server.js
@@ -36,16 +36,24 @@ let VENDOR_MAP = new Map();   // slug -> { names: [vendor strings], count }
 let vendorMapAt = 0;
 async function refreshVendorMap() {
   if (Date.now() - vendorMapAt < 15 * 60 * 1000 && VENDOR_MAP.size) return;
+  // Per vendor: LIVE count (what all.dw shows by default) and TOTAL count. all.dw
+  // hides Archived + DELETED_FROM_SHOPIFY, so a vendor with live=0 but total>0 is
+  // archived-only — we still give it an all.dw site, just landing on the Archived
+  // lifecycle so the grid isn't empty (see routing).
   const { rows } = await pool.query(
-    `select vendor, count(*)::int as n from shopify_products
-     where vendor is not null and vendor <> '' group by vendor`);
+    `select vendor,
+            count(*) filter (where upper(coalesce(status,'')) not in ('ARCHIVED','DELETED_FROM_SHOPIFY'))::int as live,
+            count(*)::int as total
+     from shopify_products
+     where vendor is not null and vendor <> ''
+     group by vendor`);
   const m = new Map();
   for (const r of rows) {
     if (BANNED.test(r.vendor)) continue;
     const slug = slugify(r.vendor);
     if (!slug) continue;
-    const e = m.get(slug) || { names: [], count: 0 };
-    e.names.push(r.vendor); e.count += r.n;
+    const e = m.get(slug) || { names: [], live: 0, total: 0 };
+    e.names.push(r.vendor); e.live += r.live; e.total += r.total;
     m.set(slug, e);
   }
   VENDOR_MAP = m; vendorMapAt = Date.now();
@@ -199,6 +207,7 @@ load(true);
 }
 
 async function indexPage() {
+  await refreshVendorMap();
   const { rows } = await pool.query(`
     select t.table_name, coalesce(c.reltuples,0)::bigint as est
     from information_schema.tables t
@@ -206,26 +215,66 @@ async function indexPage() {
     where t.table_schema='public' and t.table_name like '%\\_catalog'
     order by t.table_name`);
   const suffix = PORT === 80 ? '' : ':' + PORT;
-  const items = rows.map(r => {
-    const slug = r.table_name.replace(/_catalog$/, '').replace(/_/g, '-');
-    const dedicated = ROUTES[slug] ? ' <span class="ded">dedicated :' + ROUTES[slug] + '</span>' : '';
-    return `<li><a href="//${slug}.internal.dw${suffix}/">${esc(slug)}.internal.dw</a> <span class="n">~${Number(r.est).toLocaleString()}</span>${dedicated}</li>`;
-  });
-  const extra = Object.entries(ROUTES)
-    .filter(([slug]) => !rows.some(r => r.table_name === slug.replace(/-/g, '_') + '_catalog'))
-    .map(([slug, port]) => `<li><a href="//${slug}.internal.dw${suffix}/">${esc(slug)}.internal.dw</a> <span class="ded">dedicated :${port}</span></li>`);
-  return `<!DOCTYPE html><html><head><meta charset="utf-8"><title>internal.dw — vendor index</title>
+  const catalogSlugs = new Set(rows.map(r => r.table_name.replace(/_catalog$/, '').replace(/_/g, '-')));
+
+  const li = (slug, badge, n) =>
+    `<li><a href="//${slug}.internal.dw${suffix}/">${esc(slug)}.internal.dw</a>` +
+    (n != null ? ` <span class="n">${Number(n).toLocaleString()}</span>` : '') + ` ${badge}</li>`;
+
+  const catalogSet = catalogSlugs;
+  const liveVendor = (slug) => { const v = VENDOR_MAP.get(slug); return v && v.live > 0; };
+
+  // Group 1 — bespoke dedicated viewers.
+  const dedicated = Object.entries(ROUTES)
+    .sort((a, b) => a[0].localeCompare(b[0]))
+    .map(([slug, port]) => li(slug, `<span class="ded">dedicated :${port}</span>`, null));
+
+  // Group 2 — all.dw-schema vendors with LIVE shopify rows (not dedicated).
+  const alldw = [...VENDOR_MAP.entries()]
+    .filter(([slug, e]) => !ROUTES[slug] && e.live > 0)
+    .sort((a, b) => a[0].localeCompare(b[0]))
+    .map(([slug, e]) => li(slug, `<span class="all">all.dw</span>`, e.live));
+
+  // Group 3 — catalog-backed lines (a *_catalog table, not dedicated, not a live vendor).
+  const catalogOnly = rows
+    .map(r => [r.table_name.replace(/_catalog$/, '').replace(/_/g, '-'), Number(r.est)])
+    .filter(([slug]) => !ROUTES[slug] && !liveVendor(slug))
+    .map(([slug, est]) => li(slug, `<span class="cat">catalog</span>`, est));
+
+  // Group 4 — archived-only shopify vendors (no live rows, no catalog table) → all.dw Archived view.
+  const archived = [...VENDOR_MAP.entries()]
+    .filter(([slug, e]) => !ROUTES[slug] && e.live === 0 && e.total > 0 && !catalogSet.has(slug))
+    .sort((a, b) => a[0].localeCompare(b[0]))
+    .map(([slug, e]) => li(slug, `<span class="arc">archived</span>`, e.total));
+
+  const total = dedicated.length + alldw.length + catalogOnly.length + archived.length;
+  const section = (title, arr) => arr.length
+    ? `<h2>${title} <span class="cnt">${arr.length}</span></h2><ul>${arr.join('')}</ul>` : '';
+
+  return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>internal.dw — ${total} vendor sites</title>
 <style>body{font-family:-apple-system,Helvetica,Arial,sans-serif;background:#f5f4f1;color:#1e2a3a;margin:0}
 header{background:#1e2a3a;color:#fff;padding:16px 24px}h1{font-size:18px;margin:0}
-ul{columns:3;list-style:none;padding:20px 24px;margin:0;font-size:14px;line-height:1.9}
+h2{font-size:13px;text-transform:uppercase;letter-spacing:.5px;color:#555;margin:22px 24px 4px;font-weight:600}
+h2 .cnt{color:#aaa;font-weight:400}
+ul{columns:3;list-style:none;padding:6px 24px 4px;margin:0;font-size:14px;line-height:1.85}
 a{color:#1e2a3a;text-decoration:none}a:hover{text-decoration:underline}
-.n{color:#999;font-size:12px}.ded{color:#2e7d32;font-size:11px;border:1px solid #2e7d32;border-radius:4px;padding:0 4px}
+.n{color:#999;font-size:12px}
+.ded{color:#2e7d32;font-size:10px;border:1px solid #2e7d32;border-radius:4px;padding:0 4px}
+.all{color:#1e4b8a;font-size:10px;border:1px solid #1e4b8a;border-radius:4px;padding:0 4px}
+.cat{color:#8a6d1e;font-size:10px;border:1px solid #8a6d1e;border-radius:4px;padding:0 4px}
+.arc{color:#777;font-size:10px;border:1px solid #999;border-radius:4px;padding:0 4px}
 @media(max-width:900px){ul{columns:2}}@media(max-width:600px){ul{columns:1}}</style></head>
-<body><header><h1>internal.dw — ${rows.length + extra.length} vendor internal sites</h1></header>
-<ul>${extra.join('')}${items.join('')}</ul></body></html>`;
+<body><header><h1>internal.dw — ${total} vendor internal sites</h1></header>
+${section('Dedicated viewers', dedicated)}
+${section('all.dw schema · shopify vendors', alldw)}
+${section('Catalog-backed lines', catalogOnly)}
+${section('Archived lines', archived)}
+</body></html>`;
 }
 
 // ---------- proxy ----------
+// Plain pass-through to a dedicated viewer port.
 function proxy(req, res, port) {
   const opts = {
     host: '127.0.0.1', port, method: req.method, path: req.url,
@@ -236,6 +285,68 @@ function proxy(req, res, port) {
   req.pipe(up);
 }
 
+// Vendor-locked proxy to the local all.dw instance. `names` = exact vendor
+// string(s) from shopify_products.vendor. We FORCE ?vendors=<names> on the
+// /api/products + /api/facets calls (overriding whatever the SPA sent), so the
+// grid can only ever show this vendor — no client cooperation, no localStorage
+// dependency. The root HTML gets a small injected banner + the (now inert)
+// vendor facet row hidden.
+function proxyAllDw(req, res, slug, names, count, opts = {}) {
+  const url = new URL(req.url, 'http://x');
+  const isApi = url.pathname === '/api/products' || url.pathname === '/api/facets';
+  if (isApi) {
+    url.searchParams.set('vendors', names.join(','));
+    // Archived-only vendor: force the Archived lifecycle so the grid isn't empty.
+    if (opts.lifecycles) url.searchParams.set('lifecycles', opts.lifecycles);
+  }
+  const path = isApi ? url.pathname + '?' + url.searchParams.toString() : req.url;
+
+  const label = names[0] || slug;
+  const isHtml = url.pathname === '/' || url.pathname === '/index.html';
+
+  const reqOpts = {
+    host: '127.0.0.1', port: ALLDW_PORT, method: req.method, path,
+    headers: { ...req.headers, host: '127.0.0.1:' + ALLDW_PORT,
+      // gzip would defeat the HTML injection; ask upstream for identity on the doc.
+      ...(isHtml ? { 'accept-encoding': 'identity' } : {}) },
+  };
+
+  const up = http.request(reqOpts, (ur) => {
+    const ctype = ur.headers['content-type'] || '';
+    if (isHtml && /text\/html/i.test(ctype) && ur.statusCode === 200) {
+      const chunks = [];
+      ur.on('data', (c) => chunks.push(c));
+      ur.on('end', () => {
+        let html = Buffer.concat(chunks).toString('utf8');
+        const archNote = opts.lifecycles ? ' · archived line' : '';
+        const seedLc = opts.lifecycles
+          ? `localStorage.setItem('all_lifecycles',JSON.stringify([${JSON.stringify(opts.lifecycles)}]));` : '';
+        const inject = `<style>details.sec[data-k="vendor"]{display:none!important}</style>` +
+          `<script>try{localStorage.setItem('all_vendors',JSON.stringify(${JSON.stringify(names)}));${seedLc}` +
+          `document.title=${JSON.stringify(label + ' — internal.dw')};}catch(e){}</script>` +
+          `<div style="position:fixed;left:0;right:0;bottom:0;z-index:9999;background:#1e2a3a;color:#fff;` +
+          `font:600 12px -apple-system,Helvetica,Arial;padding:5px 14px;display:flex;gap:12px;align-items:center">` +
+          `<span>🏷️ ${label.replace(/</g, '&lt;')} · internal.dw</span>` +
+          `<span style="opacity:.65;font-weight:400">${(count || 0).toLocaleString()} products · all.dw schema${archNote}</span>` +
+          `<a href="//internal.dw:${PORT}/" style="color:#9fc2e8;text-decoration:none;margin-left:auto">← all vendors</a></div>`;
+        html = html.includes('</body>') ? html.replace('</body>', inject + '</body>') : html + inject;
+        const headers = { ...ur.headers, 'content-length': Buffer.byteLength(html) };
+        delete headers['content-encoding'];
+        res.writeHead(ur.statusCode, headers);
+        res.end(html);
+      });
+      return;
+    }
+    res.writeHead(ur.statusCode, ur.headers);
+    ur.pipe(res);
+  });
+  up.on('error', () => {
+    res.writeHead(502, { 'content-type': 'text/plain' });
+    res.end('all.dw (all-dw-local :' + ALLDW_PORT + ') is down');
+  });
+  req.pipe(up);
+}
+
 // ---------- server ----------
 const server = http.createServer(async (req, res) => {
   try {
@@ -255,12 +366,27 @@ const server = http.createServer(async (req, res) => {
       return res.end(await indexPage());
     }
 
+    // 1. Bespoke dedicated viewers (luxury microsite lines etc.) keep their own app.
     if (ROUTES[slug]) return proxy(req, res, ROUTES[slug]);
 
+    await refreshVendorMap();
+    const vinfo = VENDOR_MAP.get(slug);
     const info = await resolveTable(slug);
-    if (!info) {
+
+    // 2. Vendor with LIVE shopify rows → all.dw schema/UI, pre-filtered. The standard.
+    if (vinfo && vinfo.live > 0) return proxyAllDw(req, res, slug, vinfo.names, vinfo.live);
+
+    // 3. A *_catalog table (scraped/live lines, incl. vendors whose shopify line is
+    //    fully archived like Arte) → the generic dw_unified catalog viewer.
+    if (info) {
+      // fall through to the generic viewer below
+    } else if (vinfo && vinfo.total > 0) {
+      // 4. Archived-only vendor with no catalog table → all.dw locked to its
+      //    Archived lifecycle so the grid still shows its (archived) products.
+      return proxyAllDw(req, res, slug, vinfo.names, vinfo.total, { lifecycles: 'Archived' });
+    } else {
       res.writeHead(404, { 'content-type': 'text/plain' });
-      return res.end(`no dedicated viewer and no table ${slug.replace(/-/g, '_')}_catalog in dw_unified`);
+      return res.end(`no internal site for "${slug}": no dedicated viewer, no shopify vendor, no ${slug.replace(/-/g, '_')}_catalog table`);
     }
 
     const url = new URL(req.url, 'http://x');

← b3475ec auto-save: 2026-07-23T11:20:37 (1 files) — server.js  ·  back to Dw Internal Gateway  ·  gateway: alias lilycolor.internal.dw → japan-viewer pre-filt 5ac5875 →