← back to Dw Universe
server.js
183 lines
// DW Universe — fleet-wide search + constellation across all DW family niche sites.
// Boots by reading every ~/Projects/<slug>/data/products.json into memory once.
// Exposes /api/search and /api/sites; serves single-page UI from /public.
const express = require('express');
const fs = require('fs');
const path = require('path');
const os = require('os');
const PORT = process.env.PORT || 9466;
const HOME = os.homedir();
// 41 active niches (3 redirected ones excluded: recycled, string, embroidered).
// Category groups the constellation visually + tints each node.
const NICHES = [
// material
{ slug: 'silkwallpaper', label: 'Silk', cat: 'material' },
{ slug: 'silkwallcoverings', label: 'Silk Wallcoverings', cat: 'material' },
{ slug: 'linenwallpaper', label: 'Linen', cat: 'material' },
{ slug: 'jutewallpaper', label: 'Jute', cat: 'material' },
{ slug: 'raffiawallcoverings', label: 'Raffia', cat: 'material' },
{ slug: 'raffiawalls', label: 'Raffia Walls', cat: 'material' },
{ slug: 'corkwallcovering', label: 'Cork', cat: 'material' },
{ slug: 'fabricwallpaper', label: 'Fabric', cat: 'material' },
{ slug: 'textilewallpaper', label: 'Textile', cat: 'material' },
{ slug: 'metallicwallpaper', label: 'Metallic', cat: 'material' },
{ slug: 'silverleafwallpaper', label: 'Silver Leaf', cat: 'material' },
{ slug: 'vinylwallpaper', label: 'Vinyl', cat: 'material' },
{ slug: 'micawallpaper', label: 'Mica', cat: 'material' },
{ slug: 'madagascarwallpaper', label: 'Madagascar', cat: 'material' },
{ slug: 'mylarwallpaper', label: 'Mylar', cat: 'material' },
{ slug: 'suedewallpaper', label: 'Suede', cat: 'material' },
// decade
{ slug: '1800swallpaper', label: '1800s', cat: 'decade' },
{ slug: '1890swallpaper', label: '1890s', cat: 'decade' },
{ slug: '1900swallpaper', label: '1900s', cat: 'decade' },
{ slug: '1920swallpaper', label: '1920s', cat: 'decade' },
{ slug: '1930swallpaper', label: '1930s', cat: 'decade' },
{ slug: '1940swallpaper', label: '1940s', cat: 'decade' },
{ slug: '1950swallpaper', label: '1950s', cat: 'decade' },
{ slug: '1960swallpaper', label: '1960s', cat: 'decade' },
{ slug: '1970swallpaper', label: '1970s', cat: 'decade' },
{ slug: '1980swallpaper', label: '1980s', cat: 'decade' },
{ slug: 'retrowalls', label: 'Retro Walls', cat: 'decade' },
{ slug: 'wallpapersback', label: 'Wallpapers Back', cat: 'decade' },
// craft / aesthetic
{ slug: 'agedwallpaper', label: 'Aged', cat: 'craft' },
{ slug: 'handcraftedwallpaper', label: 'Handcrafted', cat: 'craft' },
{ slug: 'museumwallpaper', label: 'Museum', cat: 'craft' },
{ slug: 'restorationwallpaper', label: 'Restoration', cat: 'craft' },
{ slug: 'pastelwallpaper', label: 'Pastel', cat: 'craft' },
{ slug: 'glitterwalls', label: 'Glitter', cat: 'craft' },
{ slug: 'greenwallcoverings', label: 'Green', cat: 'craft' },
{ slug: 'naturalwallcoverings', label: 'Natural', cat: 'craft' },
{ slug: 'saloonwallpaper', label: 'Saloon', cat: 'craft' },
// use-case
{ slug: 'contractwallpaper', label: 'Contract', cat: 'use' },
{ slug: 'hotelwallcoverings', label: 'Hotel', cat: 'use' },
{ slug: 'hospitalitywallpaper', label: 'Hospitality', cat: 'use' },
{ slug: 'healthcarewallpaper', label: 'Healthcare', cat: 'use' },
{ slug: 'restaurantwallpaper', label: 'Restaurant', cat: 'use' },
{ slug: 'architecturalwallcoverings', label: 'Architectural', cat: 'use' },
];
// Boot: load every site's product cache + niche poems + neighbor map + pair rules.
let POEMS = {}, NEIGHBORS = {}, PAIR_RULES = {}, PAIR_BLURBS = [];
try { POEMS = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'niche-poems.json'), 'utf8')); } catch (e) { console.warn('[dw-universe] niche-poems.json:', e.message); }
try { NEIGHBORS = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'niche-neighbors.json'), 'utf8')); } catch (e) { console.warn('[dw-universe] niche-neighbors.json:', e.message); }
try {
const pr = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'pair-rules.json'), 'utf8'));
PAIR_RULES = pr.rules || {};
PAIR_BLURBS = pr.blurbs || [];
} catch (e) { console.warn('[dw-universe] pair-rules.json:', e.message); }
const DB = []; // flattened: each row tagged with slug + label + cat
const SITE_STATS = {}; // slug -> { count, label, cat, domain, poem }
for (const n of NICHES) {
const f = path.join(HOME, 'Projects', n.slug, 'data', 'products.json');
let products = [];
try { products = JSON.parse(fs.readFileSync(f, 'utf8')); } catch (e) { /* skip missing */ }
SITE_STATS[n.slug] = {
count: products.length, label: n.label, cat: n.cat,
domain: n.slug + '.com', poem: POEMS[n.slug] || '',
neighbors: NEIGHBORS[n.slug] || [],
};
for (const p of products) DB.push({ ...p, _slug: n.slug, _niche: n.label, _cat: n.cat });
}
console.log(`[dw-universe] loaded ${DB.length} products across ${NICHES.length} niches`);
// Search: simple substring match across title/tags/vendor with niche-aware ranking.
function search(q, limit = 24) {
if (!q) return [];
const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
const scored = [];
for (const p of DB) {
const hay = [p.title || '', (p.tags || []).join(' '), p.vendor || '', p._niche]
.join(' ').toLowerCase();
let score = 0;
for (const t of terms) {
if (!hay.includes(t)) { score = 0; break; }
if ((p.title || '').toLowerCase().includes(t)) score += 3;
else if ((p.tags || []).some(tg => tg.toLowerCase().includes(t))) score += 2;
else score += 1;
}
if (score > 0) scored.push([score, p]);
}
scored.sort((a, b) => b[0] - a[0]);
return scored.slice(0, limit).map(([s, p]) => ({
sku: p.sku, title: p.title, image_url: p.image_url, vendor: p.vendor,
max_price: p.max_price, niche: p._niche, slug: p._slug,
product_url: p.product_url || `https://${p._slug}.com/`,
}));
}
const app = express();
app.disable('x-powered-by');
app.get('/api/search', (req, res) => {
const q = String(req.query.q || '').slice(0, 200);
const t = Date.now();
const results = search(q, 24);
res.json({ q, count: results.length, ms: Date.now() - t, results });
});
app.get('/api/sites', (req, res) => {
const total = Object.values(SITE_STATS).reduce((s, x) => s + x.count, 0);
res.json({ total_products: total, niches: SITE_STATS });
});
// Pairing Oracle — given a sku from any niche, return one cross-niche soulmate + an editorial blurb.
app.get('/api/pair', (req, res) => {
const sku = String(req.query.sku || '').slice(0, 200);
const slug = String(req.query.slug || '').slice(0, 64);
if (!sku) return res.status(400).json({ error: 'sku required' });
const source = DB.find(p => p.sku === sku && (!slug || p._slug === slug));
if (!source) return res.status(404).json({ error: 'source product not found' });
const candidates = PAIR_RULES[source._slug] || [];
// P1: filter candidates + fallback to only niches with loaded products
const validCandidates = candidates.filter(s => SITE_STATS[s] && SITE_STATS[s].count > 0);
let targetSlug = validCandidates[Math.floor(Math.random() * validCandidates.length)];
if (!targetSlug) {
const otherSlugs = NICHES.map(n => n.slug).filter(s => s !== source._slug && SITE_STATS[s] && SITE_STATS[s].count > 0);
targetSlug = otherSlugs[Math.floor(Math.random() * otherSlugs.length)];
}
const pool = DB.filter(p => p._slug === targetSlug && p.image_url);
if (!pool.length) return res.status(404).json({ error: 'no candidates in target niche' });
const paired = pool[Math.floor(Math.random() * pool.length)];
const blurb = PAIR_BLURBS[Math.floor(Math.random() * PAIR_BLURBS.length)] || '';
function shape(p) {
return {
sku: p.sku, title: p.title, image_url: p.image_url, vendor: p.vendor,
max_price: p.max_price, niche: p._niche, slug: p._slug,
product_url: p.product_url || `https://${p._slug}.com/`,
};
}
res.json({ source: shape(source), paired: shape(paired), blurb });
});
app.get('/api/sample', (req, res) => {
// 24 random products to seed the page before user types
const out = [];
const seen = new Set();
while (out.length < 24 && seen.size < DB.length) {
const i = Math.floor(Math.random() * DB.length);
if (seen.has(i)) continue;
seen.add(i);
const p = DB[i];
if (!p.image_url) continue;
out.push({
sku: p.sku, title: p.title, image_url: p.image_url, vendor: p.vendor,
max_price: p.max_price, niche: p._niche, slug: p._slug,
product_url: p.product_url || `https://${p._slug}.com/`,
});
}
res.json({ results: out });
});
app.use(express.static(path.join(__dirname, 'public')));
app.listen(PORT, '127.0.0.1', () => {
console.log(`[dw-universe] http://127.0.0.1:${PORT}/ (${DB.length} products · ${NICHES.length} niches)`);
});