← back to All Dw Landing
auto-save: 2026-07-02T09:16:46 (4 files) — .gitignore config/ scripts/ server.js
9107b64af6abe329ad8d4adce83c375dffb02a12 · 2026-07-02 09:16:49 -0700 · Steve Abrams
Files touched
A .gitignoreA config/known-subdomains.jsonA scripts/build-sites-json.jsA server.js
Diff
commit 9107b64af6abe329ad8d4adce83c375dffb02a12
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 09:16:49 2026 -0700
auto-save: 2026-07-02T09:16:46 (4 files) — .gitignore config/ scripts/ server.js
---
.gitignore | 9 +++
config/known-subdomains.json | 40 +++++++++++++
scripts/build-sites-json.js | 122 ++++++++++++++++++++++++++++++++++++++
server.js | 137 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 308 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bb5ec89
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+data/probe-results.json
diff --git a/config/known-subdomains.json b/config/known-subdomains.json
new file mode 100644
index 0000000..c19434d
--- /dev/null
+++ b/config/known-subdomains.json
@@ -0,0 +1,40 @@
+{
+ "_comment": "Hand-maintained registry of *.designerwallcoverings.com subdomains that are KNOWN to exist (or have a local project). Brand microsites match DB vendors by slug; internal sites are non-brand tools. Edit this file, then re-run scripts/build-sites-json.js.",
+ "brands": [
+ {
+ "slug": "artmura",
+ "vendor": "Artmura",
+ "note": "LIVE reference build for the dw-vendor-landing template",
+ "hasMicrosite": true
+ },
+ {
+ "slug": "astek",
+ "vendor": "Astek",
+ "knownProject": "~/Projects/astek-landing",
+ "localUrl": "http://127.0.0.1:9944",
+ "note": "Internal-only editorial landing (Basic-Auth), running on Mac2 :9944",
+ "hasMicrosite": true
+ },
+ {
+ "slug": "reidwitlin",
+ "vendor": "Reid Witlin",
+ "knownProject": "~/Projects/reidwitlin-landing",
+ "note": "Local project built; deploy status tracked by probe",
+ "hasMicrosite": true
+ },
+ {
+ "slug": "quadrille",
+ "vendor": "Quadrille",
+ "knownProject": "~/Projects/quadrille-house-site",
+ "note": "Local project built; deploy status tracked by probe",
+ "hasMicrosite": true
+ }
+ ],
+ "internal": [
+ { "slug": "new", "label": "New-import catalog viewer", "note": "DO NOT TOUCH the new-import-viewer project (live Shopify write machinery)" },
+ { "slug": "pairs", "label": "Pairs-well-with viewer" },
+ { "slug": "chat", "label": "Chat agent" },
+ { "slug": "appointments", "label": "Appointments / smart scheduling" },
+ { "slug": "all", "label": "This directory site (all-dw-landing)" }
+ ]
+}
diff --git a/scripts/build-sites-json.js b/scripts/build-sites-json.js
new file mode 100644
index 0000000..fc7c166
--- /dev/null
+++ b/scripts/build-sites-json.js
@@ -0,0 +1,122 @@
+#!/usr/bin/env node
+/**
+ * all.designerwallcoverings.com — build data/sites.json
+ *
+ * READ-ONLY query against the LOCAL dw_unified mirror: every distinct vendor
+ * with active products (count + one sample product image), slugified to its
+ * candidate <slug>.designerwallcoverings.com subdomain, merged with the
+ * hand-maintained config/known-subdomains.json (existing microsites +
+ * non-brand internal subdomains, marked type:"internal").
+ *
+ * Output: data/sites.json [{slug, vendor, activeProducts, sampleImage, type, knownProject?, ...}]
+ * $0 (local PG read).
+ */
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { Pool } = require('pg');
+
+const OUT = path.join(__dirname, '..', 'data', 'sites.json');
+const KNOWN = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'known-subdomains.json'), 'utf8'));
+
+// Same connection pattern as astek-landing/scripts/build-products-json.js
+const PW = (() => {
+ try {
+ const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
+ const m = env.match(/^DW_ADMIN_DB_PASSWORD=(.*)$/m);
+ if (m) return m[1].replace(/^["']|["']$/g, '').trim();
+ } catch {}
+ return process.env.PGPASSWORD || '';
+})();
+const pool = process.env.DATABASE_URL
+ ? new Pool({ connectionString: process.env.DATABASE_URL })
+ : new Pool({ host: '127.0.0.1', port: 5432, user: 'dw_admin', database: 'dw_unified', password: PW });
+
+// Banned-word scrub on every DISPLAY string (standing rule). Raw vendor stays
+// internal for slug derivation only.
+const clean = s => (s == null ? s : String(s)
+ .replace(/\bWallpapers\b/gi, 'Wallcoverings')
+ .replace(/\bWallpaper\b/gi, 'Wallcovering'));
+
+// vendor -> candidate subdomain slug: lowercase, alphanumeric only.
+const slugify = v => String(v || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/&/g, 'and').replace(/[^a-z0-9]/g, '');
+
+async function main() {
+ // One row per vendor with ≥1 ACTIVE product; sample image = most recent non-null image.
+ const { rows } = await pool.query(`
+ SELECT vendor,
+ COUNT(*)::int AS active_products,
+ (ARRAY_REMOVE(ARRAY_AGG(image_url ORDER BY created_at_shopify DESC NULLS LAST), NULL))[1] AS sample_image
+ FROM shopify_products
+ WHERE UPPER(status) = 'ACTIVE' AND vendor IS NOT NULL AND vendor <> ''
+ GROUP BY vendor
+ ORDER BY COUNT(*) DESC
+ `);
+
+ const knownBrands = new Map((KNOWN.brands || []).map(b => [b.slug, b]));
+ const sites = [];
+ const seenSlugs = new Set();
+
+ for (const r of rows) {
+ const slug = slugify(r.vendor);
+ if (!slug || seenSlugs.has(slug)) { if (seenSlugs.has(slug)) console.warn(` ! slug collision skipped: ${r.vendor} -> ${slug}`); continue; }
+ seenSlugs.add(slug);
+ const k = knownBrands.get(slug) || {};
+ sites.push({
+ slug,
+ vendor: clean(r.vendor),
+ activeProducts: r.active_products,
+ sampleImage: r.sample_image || null,
+ type: 'brand',
+ hasMicrosite: !!k.hasMicrosite,
+ knownProject: k.knownProject || null,
+ localUrl: k.localUrl || null,
+ note: k.note || null,
+ });
+ }
+
+ // Known brand sites whose vendor slug didn't come out of the active query still get a card.
+ for (const [slug, k] of knownBrands) {
+ if (seenSlugs.has(slug)) continue;
+ seenSlugs.add(slug);
+ sites.push({
+ slug, vendor: clean(k.vendor || slug), activeProducts: 0, sampleImage: null,
+ type: 'brand', hasMicrosite: !!k.hasMicrosite,
+ knownProject: k.knownProject || null, localUrl: k.localUrl || null, note: k.note || null,
+ });
+ }
+
+ // Non-brand internal subdomains.
+ for (const i of (KNOWN.internal || [])) {
+ if (seenSlugs.has(i.slug)) continue;
+ seenSlugs.add(i.slug);
+ sites.push({
+ slug: i.slug, vendor: clean(i.label || i.slug), activeProducts: null, sampleImage: null,
+ type: 'internal', hasMicrosite: null, knownProject: i.knownProject || null,
+ localUrl: i.localUrl || null, note: i.note || null,
+ });
+ }
+
+ const brands = sites.filter(s => s.type === 'brand');
+ const activeBrands = brands.filter(s => s.activeProducts > 0);
+ const withMicrosite = activeBrands.filter(s => s.hasMicrosite);
+
+ const snapshot = {
+ built_at: new Date().toISOString(),
+ source: 'dw_unified.shopify_products (local mirror, read-only) + config/known-subdomains.json',
+ counts: {
+ sites: sites.length,
+ brands: brands.length,
+ activeBrands: activeBrands.length,
+ internal: sites.length - brands.length,
+ brandsWithMicrosite: withMicrosite.length,
+ },
+ sites,
+ };
+ fs.writeFileSync(OUT, JSON.stringify(snapshot, null, 1));
+ console.log(`sites.json -> ${OUT}`);
+ console.log(` sites: ${sites.length} (brands: ${brands.length}, active brands: ${activeBrands.length}, internal: ${sites.length - brands.length})`);
+ console.log(` rollout: ${withMicrosite.length} of ${activeBrands.length} active brands have a microsite (${(100 * withMicrosite.length / Math.max(1, activeBrands.length)).toFixed(1)}%)`);
+ await pool.end();
+}
+main().catch(e => { console.error(e); process.exit(1); });
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..16ff472
--- /dev/null
+++ b/server.js
@@ -0,0 +1,137 @@
+/* all.designerwallcoverings.com — internal Basic-Auth directory of every
+ *.designerwallcoverings.com brand-microsite line (rollout tracker).
+ Serves data/sites.json merged with periodic live HTTPS probes of each
+ subdomain. No Cloudflare API dependency. READ-ONLY against the world. */
+const express = require('express');
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = Number(process.env.PORT || 9946);
+const DATA = path.join(__dirname, 'data', 'sites.json');
+const PROBE_CACHE = path.join(__dirname, 'data', 'probe-results.json');
+const PROBE_INTERVAL_SEC = Number(process.env.PROBE_INTERVAL_SEC || 900); // 15 min
+const PROBE_TIMEOUT_MS = Number(process.env.PROBE_TIMEOUT_MS || 5000);
+const PROBE_CONCURRENCY = Number(process.env.PROBE_CONCURRENCY || 10);
+
+const app = express();
+
+// HTTP Basic Auth — internal-only gate. Override via BASIC_AUTH="user:pass".
+const [AUTH_USER, AUTH_PASS] = (process.env.BASIC_AUTH || 'admin:DW2024!').split(':');
+app.use((req, res, next) => {
+ const hdr = req.headers.authorization || '';
+ const [scheme, b64] = hdr.split(' ');
+ if (scheme === 'Basic' && b64) {
+ const [u, p] = Buffer.from(b64, 'base64').toString('utf8').split(':');
+ if (u === AUTH_USER && p === AUTH_PASS) return next();
+ }
+ res.set('WWW-Authenticate', 'Basic realm="All DW Microsites (internal)"');
+ return res.status(401).send('Authentication required.');
+});
+
+app.use(express.json({ limit: '64kb' }));
+
+// ── manifest ────────────────────────────────────────────────────────────────
+let SNAP = { counts: {}, sites: [] };
+function load() {
+ SNAP = JSON.parse(fs.readFileSync(DATA, 'utf8'));
+ console.log(`[all-dw] loaded ${SNAP.sites.length} sites (built ${SNAP.built_at})`);
+}
+load();
+fs.watchFile(DATA, { interval: 5000 }, (cur, prev) => {
+ if (cur.mtimeMs !== prev.mtimeMs) { try { load(); } catch (e) { console.error('[all-dw] reload failed:', e.message); } }
+});
+
+// ── probe loop ──────────────────────────────────────────────────────────────
+// status per slug: PROBING (not yet probed) | LIVE-GATED (401) | LIVE-OPEN (2xx/3xx)
+// | BROKEN (404/other 4xx/5xx) | MISSING (DNS / SSL / timeout / conn failure)
+let PROBES = {}; // slug -> { status, httpStatus, detail, lastProbe, ms }
+try { PROBES = JSON.parse(fs.readFileSync(PROBE_CACHE, 'utf8')); } catch {}
+let LAST_CYCLE = null; // ISO of last completed full cycle
+let CYCLE_RUNNING = false;
+
+function probeOne(slug) {
+ return new Promise((resolve) => {
+ const host = `${slug}.designerwallcoverings.com`;
+ const t0 = Date.now();
+ const done = (status, httpStatus, detail) => resolve({
+ status, httpStatus: httpStatus || null, detail: detail || null,
+ lastProbe: new Date().toISOString(), ms: Date.now() - t0,
+ });
+ const req = https.get({ host, path: '/', timeout: PROBE_TIMEOUT_MS, headers: { 'User-Agent': 'all-dw-landing-probe/1.0 (internal rollout tracker)' } }, (res) => {
+ res.resume(); // discard body
+ const c = res.statusCode;
+ if (c === 401) return done('LIVE-GATED', c, 'exists behind auth');
+ if (c >= 200 && c < 400) return done('LIVE-OPEN', c);
+ return done('BROKEN', c, `HTTP ${c}`);
+ });
+ req.on('timeout', () => { req.destroy(new Error('timeout')); });
+ req.on('error', (e) => done('MISSING', null, e.code || e.message));
+ });
+}
+
+async function runProbeCycle(reason) {
+ if (CYCLE_RUNNING) return { skipped: true, reason: 'cycle already running' };
+ CYCLE_RUNNING = true;
+ const slugs = SNAP.sites.map(s => s.slug);
+ console.log(`[all-dw] probe cycle start (${slugs.length} subdomains, reason=${reason})`);
+ const queue = [...slugs];
+ const workers = Array.from({ length: Math.min(PROBE_CONCURRENCY, queue.length) }, async () => {
+ while (queue.length) {
+ const slug = queue.shift();
+ PROBES[slug] = await probeOne(slug);
+ }
+ });
+ await Promise.all(workers);
+ LAST_CYCLE = new Date().toISOString();
+ try { fs.writeFileSync(PROBE_CACHE, JSON.stringify(PROBES)); } catch {}
+ CYCLE_RUNNING = false;
+ const tally = {};
+ for (const p of Object.values(PROBES)) tally[p.status] = (tally[p.status] || 0) + 1;
+ console.log(`[all-dw] probe cycle done: ${JSON.stringify(tally)}`);
+ return { done: true, tally };
+}
+runProbeCycle('boot'); // serve immediately; results fill in
+setInterval(() => runProbeCycle('interval'), PROBE_INTERVAL_SEC * 1000).unref();
+
+// ── API ─────────────────────────────────────────────────────────────────────
+function merged() {
+ return SNAP.sites.map(s => {
+ const pr = PROBES[s.slug] || { status: 'PROBING', httpStatus: null, detail: null, lastProbe: null, ms: null };
+ return { ...s, url: `https://${s.slug}.designerwallcoverings.com/`, probe: pr };
+ });
+}
+
+app.get('/api/sites', (_req, res) => {
+ const sites = merged();
+ const brands = sites.filter(s => s.type === 'brand' && s.activeProducts > 0);
+ const live = new Set(['LIVE-GATED', 'LIVE-OPEN']);
+ const withSite = brands.filter(s => s.hasMicrosite || live.has(s.probe.status));
+ res.json({
+ built_at: SNAP.built_at,
+ counts: { ...SNAP.counts, brandsWithMicrosite: withSite.length },
+ rollout: {
+ withMicrosite: withSite.length,
+ activeBrands: brands.length,
+ pct: brands.length ? +(100 * withSite.length / brands.length).toFixed(1) : 0,
+ },
+ sites,
+ });
+});
+
+app.get('/api/meta', (_req, res) => res.json({
+ lastRefresh: LAST_CYCLE || SNAP.built_at,
+ intervalSec: PROBE_INTERVAL_SEC,
+ probing: CYCLE_RUNNING,
+ count: SNAP.sites.length,
+ now: new Date().toISOString(),
+}));
+
+app.post('/api/probe', async (_req, res) => {
+ const r = await runProbeCycle('manual');
+ res.json(r);
+});
+
+app.use(express.static(path.join(__dirname, 'public')));
+
+app.listen(PORT, () => console.log(`all-dw-landing → http://127.0.0.1:${PORT} (Basic Auth ${AUTH_USER}/***)`));
(oldest)
·
back to All Dw Landing
·
register internal viewers: quadrille :9947, artmura :9948, r cbfb266 →