← back to Chinoiseriewallpaper
stage2: wire microsite catalog + admin CRUD + /api/sliders
2fb17f428c75ef52999cf62acf4084f8ec0dce92 · 2026-05-19 10:00:56 -0700 · Steve Abrams
Files touched
M server.jsA site.config.json
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 →