[object Object]

← back to Chinoiseriewallpaper

stage2: wire microsite catalog + admin CRUD + /api/sliders

2fb17f428c75ef52999cf62acf4084f8ec0dce92 · 2026-05-19 10:00:56 -0700 · Steve Abrams

Files touched

Diff

commit 2fb17f428c75ef52999cf62acf4084f8ec0dce92
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 10:00:56 2026 -0700

    stage2: wire microsite catalog + admin CRUD + /api/sliders
---
 server.js        | 57 +++++++++++++++++++++++++++++++++++++++++++-------------
 site.config.json | 15 +++++++++++++++
 2 files changed, 59 insertions(+), 13 deletions(-)

diff --git a/server.js b/server.js
index e1a63fd..01f99f9 100644
--- a/server.js
+++ b/server.js
@@ -21,16 +21,14 @@ const fs      = require('fs');
 const PORT = process.env.PORT || 9853;
 const DW_SHOPIFY = 'https://designerwallcoverings.com';
 const DOMAIN = 'chinoiseriewallpaper.com';
+const catalog = require('../_shared/admin-catalog');
+const siteCfg = JSON.parse(fs.readFileSync(path.join(__dirname, 'site.config.json'), 'utf8'));
+const SITE_SLUG = siteCfg.slug || path.basename(__dirname);
+const SITE_RAILS = Array.isArray(siteCfg.rails) ? siteCfg.rails : [];
 
-// ── Load catalog snapshot ──────────────────────────────────────────────────
+// Catalog now lives in dw_unified.microsite_products — populated async at
+// startup (see bottom of file). Falls back to data/products.json if PG is down.
 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('not an array');
-} catch (e) {
-  console.error(`[chinoiserie] FATAL: could not load products.json — ${e.message}`);
-}
 
 // ── Strict chinoiserie filter ──────────────────────────────────────────────
 // Reject ANY texture, natural-fiber surface, or obvious non-Asian residential.
@@ -56,8 +54,8 @@ function isJunk(p) {
   return false;
 }
 
-const PRODUCTS_PRE = DATA_RAW.filter(p => !isJunk(p));
-console.log(`[chinoiserie] Loaded ${DATA_RAW.length}, after strict-chinois filter: ${PRODUCTS_PRE.length}`);
+// Populated at startup from dw_unified (see bottom of file).
+let PRODUCTS_PRE = [];
 
 // ── Color hex helpers (sort-skill canonical) ───────────────────────────────
 const COLOR_HEX = {
@@ -181,6 +179,12 @@ function sortProducts(list, mode) {
   if (mode === 'wheel')      return [...list].sort((a,b) => hue(a) - hue(b));
   if (mode === 'price-asc')  return [...list].sort((a,b) => (a.price||0) - (b.price||0));
   if (mode === 'price-desc') return [...list].sort((a,b) => (b.price||0) - (a.price||0));
+  // List-view sortable columns — every column sortable in both directions.
+  if (mode === 'title-desc')     return [...list].sort((a,b) => String(b.title||'').localeCompare(String(a.title||'')));
+  if (mode === 'sku-desc')       return [...list].sort((a,b) => String(b.sku||b.handle||'').localeCompare(String(a.sku||a.handle||'')));
+  if (mode === 'vendor-desc')    return [...list].sort((a,b) => String(b.vendor||'').localeCompare(String(a.vendor||'')) || String(a.title||'').localeCompare(String(b.title||'')));
+  if (mode === 'aesthetic')      return [...list].sort((a,b) => String(a.aesthetic||'').localeCompare(String(b.aesthetic||'')) || String(a.title||'').localeCompare(String(b.title||'')));
+  if (mode === 'aesthetic-desc') return [...list].sort((a,b) => String(b.aesthetic||'').localeCompare(String(a.aesthetic||'')) || String(a.title||'').localeCompare(String(b.title||'')));
   return list; // 'newest' — server's natural order
 }
 
@@ -237,6 +241,16 @@ app.get('/api/products', (req, res) => {
   });
 });
 
+// ── API — sliders ──────────────────────────────────────────────────────────
+app.get('/api/sliders', (req, res) => {
+  const out = [];
+  for (const a of SITE_RAILS) {
+    const items = PRODUCTS_PRE.filter(p => p.aesthetic === a).slice(0, 12);
+    if (items.length >= 4) out.push({ aesthetic: a, items: decorateColors(items) });
+  }
+  res.json({ rails: out });
+});
+
 // ── API — facets ───────────────────────────────────────────────────────────
 app.get('/api/facets', (req, res) => {
   const aesthetics = {};
@@ -272,6 +286,23 @@ app.get('/', (req, res) => {
   res.sendFile(path.join(__dirname, 'public', 'index.html'));
 });
 
-app.listen(PORT, '127.0.0.1', () => {
-  console.log(`[chinoiserie] Listening on http://127.0.0.1:${PORT} — ${PRODUCTS_PRE.length} chinois patterns`);
-});
+// 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.
+(async () => {
+  try {
+    const rows = await catalog.getProducts(SITE_SLUG);
+    PRODUCTS_PRE = rows.filter(p => !isJunk(p));
+    console.log(`[chinoiserie] loaded ${rows.length} from dw_unified, after strict-chinois filter: ${PRODUCTS_PRE.length}`);
+  } catch (e) {
+    console.error(`[chinoiserie] PG catalog load failed (${e.message}) — falling back to data/products.json`);
+    try {
+      const raw = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'products.json'), 'utf8'));
+      PRODUCTS_PRE = (Array.isArray(raw) ? raw : []).filter(p => !isJunk(p));
+    } catch (_) { PRODUCTS_PRE = []; }
+  }
+  app.listen(PORT, '127.0.0.1', () => {
+    console.log(`[chinoiserie] Listening on http://127.0.0.1:${PORT} — ${PRODUCTS_PRE.length} chinois patterns`);
+  });
+})();
diff --git a/site.config.json b/site.config.json
new file mode 100644
index 0000000..60485fe
--- /dev/null
+++ b/site.config.json
@@ -0,0 +1,15 @@
+{
+  "slug": "chinoiseriewallpaper",
+  "siteName": "Chinoiseriewallpaper",
+  "domain": "chinoiseriewallpaper.com",
+  "heroHeadline": "CHINOISERIEWALLPAPER",
+  "rails": [
+    "green",
+    "commercial",
+    "architectural",
+    "white",
+    "botanical",
+    "floral"
+  ],
+  "_generated": true
+}

← 8b1e9bd block .bak/.pre-* snapshot files from being served as static  ·  back to Chinoiseriewallpaper  ·  Phase 2: mobile-ready + tap-to-reveal 965f59f →