← back to Wallsandfabrics
stage2: wire wallsandfabrics onto shared catalog (hand-tailored)
23455867d28c9b341010f6b64b87ef6c18908b76 · 2026-05-19 11:07:54 -0700 · Steve
- catalog read-path: require _shared/admin-catalog, resolve slug from
site.config.json (slug=wallsandfabrics), async getProducts() at startup
- preserves the surface model: PG rows run through normalize() -> isJunk()
-> surfaceOf() exactly as before; SURFACE_COUNTS rebuilt per load
- catalog.mount() adds /admin/catalog (basic-auth) + /api/admin/* before listen
- JSON fallback retained; also falls back when PG rows lack image_url
(Stage-2 Phase A migration missed this site's images[] array) so the
storefront still renders real images until microsite_products is backfilled
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Files touched
Diff
commit 23455867d28c9b341010f6b64b87ef6c18908b76
Author: Steve <steve@designerwallcoverings.com>
Date: Tue May 19 11:07:54 2026 -0700
stage2: wire wallsandfabrics onto shared catalog (hand-tailored)
- catalog read-path: require _shared/admin-catalog, resolve slug from
site.config.json (slug=wallsandfabrics), async getProducts() at startup
- preserves the surface model: PG rows run through normalize() -> isJunk()
-> surfaceOf() exactly as before; SURFACE_COUNTS rebuilt per load
- catalog.mount() adds /admin/catalog (basic-auth) + /api/admin/* before listen
- JSON fallback retained; also falls back when PG rows lack image_url
(Stage-2 Phase A migration missed this site's images[] array) so the
storefront still renders real images until microsite_products is backfilled
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---
server.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 59 insertions(+), 17 deletions(-)
diff --git a/server.js b/server.js
index c07d2e4..3577140 100644
--- a/server.js
+++ b/server.js
@@ -12,14 +12,10 @@ const PORT = process.env.PORT || 9934;
const DW_SHOPIFY = 'https://designerwallcoverings.com';
const __SITE = path.basename(__dirname);
-let DATA_RAW;
-try {
- DATA_RAW = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'products.json'), 'utf8'));
- 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}`);
- 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;
+const SITE_RAILS = Array.isArray(siteCfg.rails) ? siteCfg.rails : [];
// Normalize the multiple shapes that ship across DW family datasets:
// - cork shape: image_url + max_price + product_url
@@ -47,9 +43,6 @@ function isJunk(p) {
return false;
}
-const PRODUCTS = DATA_RAW.map(normalize).filter(p => !isJunk(p));
-console.log(`[${__SITE}] Loaded ${DATA_RAW.length}, kept ${PRODUCTS.length}, dropped ${DATA_RAW.length - PRODUCTS.length}`);
-
// Surface tagging — derive a canonical surface bucket per product.
function surfaceOf(p) {
const pt = String(p.product_type || '').toLowerCase();
@@ -59,10 +52,21 @@ function surfaceOf(p) {
if (pt.includes('trim') || /trim|tassel|braid|fringe/.test(blob)) return 'trim';
return 'wallcovering';
}
-for (const p of PRODUCTS) p.__surface = surfaceOf(p);
-const SURFACE_COUNTS = PRODUCTS.reduce((acc, p) => { acc[p.__surface] = (acc[p.__surface] || 0) + 1; return acc; }, {});
-console.log(`[${__SITE}] Surfaces:`, SURFACE_COUNTS);
+// Catalog now lives in dw_unified.microsite_products — loaded async at startup
+// (see bottom of file). PG down → falls back to data/products.json. The surface
+// model is preserved: rows go through normalize() → isJunk() → surfaceOf().
+let PRODUCTS = [];
+let SURFACE_COUNTS = {};
+
+function buildCatalog(raw) {
+ const kept = raw.map(normalize).filter(p => !isJunk(p));
+ for (const p of kept) p.__surface = surfaceOf(p);
+ PRODUCTS = kept;
+ SURFACE_COUNTS = PRODUCTS.reduce((acc, p) => { acc[p.__surface] = (acc[p.__surface] || 0) + 1; return acc; }, {});
+ console.log(`[${__SITE}] Loaded ${raw.length}, kept ${PRODUCTS.length}, dropped ${raw.length - PRODUCTS.length}`);
+ console.log(`[${__SITE}] Surfaces:`, SURFACE_COUNTS);
+}
const app = express();
app.use(helmet({ contentSecurityPolicy: false }));
@@ -202,6 +206,44 @@ ${urls.map(u => ` <url><loc>https://wallsandfabrics.com${u}</loc><changefreq>we
res.type('application/xml').send(xml);
});
-app.listen(PORT, '127.0.0.1', () => {
- console.log(`[${__SITE}] listening on http://127.0.0.1:${PORT}`);
-});
+// Admin catalog CRUD — /admin/catalog (basic-auth) + /api/admin/* REST.
+catalog.mount(app, { siteSlug: SITE_SLUG, rails: SITE_RAILS });
+
+function loadJsonFallback(reason) {
+ console.error(`[${__SITE}] ${reason} — falling back to data/products.json`);
+ let raw = [];
+ try {
+ 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');
+ } catch (err) {
+ console.error(`[${__SITE}] FATAL: could not load products.json — ${err.message}`);
+ raw = [];
+ }
+ buildCatalog(raw);
+}
+
+// Load the catalog from dw_unified, then start listening.
+// • PG down / 0 rows → data/products.json fallback.
+// • PG rows present but the surface model would drop them all (Stage-2 Phase A
+// migrated wallsandfabrics metadata WITHOUT image_url — this site's source
+// JSON carries images in an images[] array the column-map missed) → also
+// fall back to JSON so the storefront still renders real product images.
+// Once microsite_products.image_url is backfilled for this slug, the PG
+// path takes over automatically with no code change.
+(async () => {
+ try {
+ const rows = await catalog.getProducts(SITE_SLUG);
+ if (!rows.length) throw new Error('microsite_products returned 0 rows');
+ buildCatalog(rows);
+ if (!PRODUCTS.length) {
+ loadJsonFallback(`PG returned ${rows.length} rows but the surface model kept 0 (no image_url in microsite_products)`);
+ } else {
+ console.log(`[${__SITE}] catalog source: dw_unified.microsite_products (${rows.length} rows)`);
+ }
+ } catch (e) {
+ loadJsonFallback(`PG catalog load failed (${e.message})`);
+ }
+ app.listen(PORT, '127.0.0.1', () => {
+ console.log(`[${__SITE}] listening on http://127.0.0.1:${PORT}`);
+ });
+})();
← 33f3b27 snapshot: site.config.json rails field from stage-1 catalog
·
back to Wallsandfabrics
·
Phase 2: fix card-render syntax error, mobile audit pass 449e634 →