[object Object]

← back to Hollywood Wallcoverings

stage2: wire hollywood-wallcoverings onto shared catalog (hand-tailored)

11b30f798eb96f9e2586664039216a7cab545d6a · 2026-05-19 11:07:45 -0700 · Steve Abrams

Files touched

Diff

commit 11b30f798eb96f9e2586664039216a7cab545d6a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 11:07:45 2026 -0700

    stage2: wire hollywood-wallcoverings onto shared catalog (hand-tailored)
---
 server.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 61 insertions(+), 15 deletions(-)

diff --git a/server.js b/server.js
index 0c54328..61c7297 100644
--- a/server.js
+++ b/server.js
@@ -3,6 +3,7 @@
  * Reads products.json, exposes search/filter/pagination, links memo-sample CTA
  * back to the DW Shopify product page (#sample anchor).
  */
+try { require('dotenv').config({ path: require('path').join(__dirname, '.env') }); } catch (e) {}
 const express = require('express');
 const helmet = require('helmet');
 const path = require('path');
@@ -10,16 +11,38 @@ const fs = require('fs');
 
 const PORT = process.env.PORT || 9832;
 const __SITE = path.basename(__dirname);
-let DATA_RAW;
-try {
-  const raw = fs.readFileSync(path.join(__dirname, 'data', 'products.json'), 'utf8');
-  DATA_RAW = JSON.parse(raw);
-  if (!Array.isArray(DATA_RAW)) throw new Error('products.json must be an array');
-} catch (e) {
-  console.error(`[${__SITE}] FATAL: could not load products.json — ${e.message}`);
-  console.error(`[${__SITE}] Starting with empty catalog. Run pull script to populate data/products.json.`);
-  DATA_RAW = [];
+const catalog = require('../_shared/admin-catalog');
+const siteCfg = JSON.parse(fs.readFileSync(path.join(__dirname, 'site.config.json'), 'utf8'));
+const SITE_SLUG = siteCfg.slug || __SITE;            // slug ≠ dir name (hollywoodwallcoverings)
+const SITE_RAILS = Array.isArray(siteCfg.rails) ? siteCfg.rails : [];
+
+// Catalog now lives in dw_unified.microsite_products. The shared module returns
+// rows shaped { sku, handle, title, vendor, product_type, image_url, tags, ... };
+// this site's render code expects { dw_sku, mfr_sku, color, ... } — normalize so
+// the existing junk filter, sort, search, and templates keep working untouched.
+function normalizeRow(r) {
+  if (!r || typeof r !== 'object') return r;
+  return {
+    ...r,
+    dw_sku: r.dw_sku || r.sku || r.handle || '',
+    mfr_sku: r.mfr_sku || '',
+    color: r.color || (Array.isArray(r.tags) ? r.tags.join(' ') : ''),
+  };
 }
+
+// Synchronous JSON read kept as a fallback only — used if the PG load fails.
+function loadFromJson() {
+  try {
+    const raw = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'products.json'), 'utf8'));
+    if (!Array.isArray(raw)) throw new Error('products.json must be an array');
+    return raw;
+  } catch (e) {
+    console.error(`[${__SITE}] products.json fallback failed — ${e.message}`);
+    return [];
+  }
+}
+
+let DATA_RAW = [];
 const DW_SHOPIFY = 'https://designerwallcoverings.com';
 
 const app = express();
@@ -67,11 +90,19 @@ function isJunk(p) {
   return false;
 }
 
-const DATA = DATA_RAW.filter(p => !isJunk(p));
-const DROPPED = DATA_RAW.length - DATA.length;
-console.log(`Loaded ${DATA_RAW.length} products, kept ${DATA.length}, dropped ${DROPPED} as junk`);
-
-const TYPES = [...new Set(DATA.map(p => NORM_TYPE(p.product_type)))].sort();
+// Populated by the async startup path at the bottom of this file. Kept as `let`
+// (was `const`) so the PG load can fill them before app.listen().
+let DATA = [];
+let DROPPED = 0;
+let TYPES = [];
+
+function rebuildCatalog(rawRows) {
+  DATA_RAW = rawRows.map(normalizeRow);
+  DATA = DATA_RAW.filter(p => !isJunk(p));
+  DROPPED = DATA_RAW.length - DATA.length;
+  TYPES = [...new Set(DATA.map(p => NORM_TYPE(p.product_type)))].sort();
+  console.log(`[${__SITE}] catalog: ${DATA_RAW.length} rows, kept ${DATA.length}, dropped ${DROPPED} as junk`);
+}
 
 const esc = s => String(s||'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
 
@@ -793,5 +824,20 @@ app.get('/trade', (_req, res) => {
 
 app.get('/health', (_req, res) => res.json({ ok: true, products: DATA.length, dropped: DROPPED, types: TYPES }));
 
+// Admin catalog CRUD — /admin/catalog (basic-auth) + /api/admin/* REST.
+catalog.mount(app, { siteSlug: SITE_SLUG, rails: SITE_RAILS });
+
+// Load the catalog from dw_unified, then start listening. PG down → JSON fallback.
 const BIND = process.env.BIND || '127.0.0.1';
-app.listen(PORT, BIND, () => console.log(`Hollywood Wallcoverings on http://${BIND}:${PORT} · ${DATA.length} products`));
+(async () => {
+  try {
+    const rows = await catalog.getProducts(SITE_SLUG);
+    if (!rows.length) throw new Error('0 rows from microsite_products');
+    rebuildCatalog(rows);
+    console.log(`[${__SITE}] loaded ${rows.length} from dw_unified (slug=${SITE_SLUG})`);
+  } catch (e) {
+    console.error(`[${__SITE}] PG catalog load failed (${e.message}) — falling back to data/products.json`);
+    rebuildCatalog(loadFromJson());
+  }
+  app.listen(PORT, BIND, () => console.log(`Hollywood Wallcoverings on http://${BIND}:${PORT} · ${DATA.length} products`));
+})();

← 26ab45f Add noreferrer to all target=_blank external links  ·  back to Hollywood Wallcoverings  ·  Phase 2: mobile-ready + tap-to-reveal 5bc9e56 →