← back to All Dw Landing
server.js
138 lines
/* all.designerwallcoverings.com — internal Basic-Auth directory of every
*.designerwallcoverings.com brand-microsite line (rollout tracker).
Serves data/sites.json merged with periodic live HTTPS probes of each
subdomain. No Cloudflare API dependency. READ-ONLY against the world. */
const express = require('express');
const https = require('https');
const fs = require('fs');
const path = require('path');
const PORT = Number(process.env.PORT || 9946);
const DATA = path.join(__dirname, 'data', 'sites.json');
const PROBE_CACHE = path.join(__dirname, 'data', 'probe-results.json');
const PROBE_INTERVAL_SEC = Number(process.env.PROBE_INTERVAL_SEC || 900); // 15 min
const PROBE_TIMEOUT_MS = Number(process.env.PROBE_TIMEOUT_MS || 5000);
const PROBE_CONCURRENCY = Number(process.env.PROBE_CONCURRENCY || 10);
const app = express();
// HTTP Basic Auth — internal-only gate. Override via BASIC_AUTH="user:pass".
const [AUTH_USER, AUTH_PASS] = (process.env.BASIC_AUTH || 'admin:DW2024!').split(':');
app.use((req, res, next) => {
const hdr = req.headers.authorization || '';
const [scheme, b64] = hdr.split(' ');
if (scheme === 'Basic' && b64) {
const [u, p] = Buffer.from(b64, 'base64').toString('utf8').split(':');
if (u === AUTH_USER && p === AUTH_PASS) return next();
}
res.set('WWW-Authenticate', 'Basic realm="All DW Microsites (internal)"');
return res.status(401).send('Authentication required.');
});
app.use(express.json({ limit: '64kb' }));
// ── manifest ────────────────────────────────────────────────────────────────
let SNAP = { counts: {}, sites: [] };
function load() {
SNAP = JSON.parse(fs.readFileSync(DATA, 'utf8'));
console.log(`[all-dw] loaded ${SNAP.sites.length} sites (built ${SNAP.built_at})`);
}
load();
fs.watchFile(DATA, { interval: 5000 }, (cur, prev) => {
if (cur.mtimeMs !== prev.mtimeMs) { try { load(); } catch (e) { console.error('[all-dw] reload failed:', e.message); } }
});
// ── probe loop ──────────────────────────────────────────────────────────────
// status per slug: PROBING (not yet probed) | LIVE-GATED (401) | LIVE-OPEN (2xx/3xx)
// | BROKEN (404/other 4xx/5xx) | MISSING (DNS / SSL / timeout / conn failure)
let PROBES = {}; // slug -> { status, httpStatus, detail, lastProbe, ms }
try { PROBES = JSON.parse(fs.readFileSync(PROBE_CACHE, 'utf8')); } catch {}
let LAST_CYCLE = null; // ISO of last completed full cycle
let CYCLE_RUNNING = false;
function probeOne(slug) {
return new Promise((resolve) => {
const host = `${slug}.designerwallcoverings.com`;
const t0 = Date.now();
const done = (status, httpStatus, detail) => resolve({
status, httpStatus: httpStatus || null, detail: detail || null,
lastProbe: new Date().toISOString(), ms: Date.now() - t0,
});
const req = https.get({ host, path: '/', timeout: PROBE_TIMEOUT_MS, headers: { 'User-Agent': 'all-dw-landing-probe/1.0 (internal rollout tracker)' } }, (res) => {
res.resume(); // discard body
const c = res.statusCode;
if (c === 401) return done('LIVE-GATED', c, 'exists behind auth');
if (c >= 200 && c < 400) return done('LIVE-OPEN', c);
return done('BROKEN', c, `HTTP ${c}`);
});
req.on('timeout', () => { req.destroy(new Error('timeout')); });
req.on('error', (e) => done('MISSING', null, e.code || e.message));
});
}
async function runProbeCycle(reason) {
if (CYCLE_RUNNING) return { skipped: true, reason: 'cycle already running' };
CYCLE_RUNNING = true;
const slugs = SNAP.sites.map(s => s.slug);
console.log(`[all-dw] probe cycle start (${slugs.length} subdomains, reason=${reason})`);
const queue = [...slugs];
const workers = Array.from({ length: Math.min(PROBE_CONCURRENCY, queue.length) }, async () => {
while (queue.length) {
const slug = queue.shift();
PROBES[slug] = await probeOne(slug);
}
});
await Promise.all(workers);
LAST_CYCLE = new Date().toISOString();
try { fs.writeFileSync(PROBE_CACHE, JSON.stringify(PROBES)); } catch {}
CYCLE_RUNNING = false;
const tally = {};
for (const p of Object.values(PROBES)) tally[p.status] = (tally[p.status] || 0) + 1;
console.log(`[all-dw] probe cycle done: ${JSON.stringify(tally)}`);
return { done: true, tally };
}
runProbeCycle('boot'); // serve immediately; results fill in
setInterval(() => runProbeCycle('interval'), PROBE_INTERVAL_SEC * 1000).unref();
// ── API ─────────────────────────────────────────────────────────────────────
function merged() {
return SNAP.sites.map(s => {
const pr = PROBES[s.slug] || { status: 'PROBING', httpStatus: null, detail: null, lastProbe: null, ms: null };
return { ...s, url: `https://${s.slug}.designerwallcoverings.com/`, probe: pr };
});
}
app.get('/api/sites', (_req, res) => {
const sites = merged();
const brands = sites.filter(s => s.type === 'brand' && s.activeProducts > 0);
const live = new Set(['LIVE-GATED', 'LIVE-OPEN']);
const withSite = brands.filter(s => s.hasMicrosite || live.has(s.probe.status));
res.json({
built_at: SNAP.built_at,
counts: { ...SNAP.counts, brandsWithMicrosite: withSite.length },
rollout: {
withMicrosite: withSite.length,
activeBrands: brands.length,
pct: brands.length ? +(100 * withSite.length / brands.length).toFixed(1) : 0,
},
sites,
});
});
app.get('/api/meta', (_req, res) => res.json({
lastRefresh: LAST_CYCLE || SNAP.built_at,
intervalSec: PROBE_INTERVAL_SEC,
probing: CYCLE_RUNNING,
count: SNAP.sites.length,
now: new Date().toISOString(),
}));
app.post('/api/probe', async (_req, res) => {
const r = await runProbeCycle('manual');
res.json(r);
});
app.use(express.static(path.join(__dirname, 'public')));
app.listen(PORT, () => console.log(`all-dw-landing → http://127.0.0.1:${PORT} (Basic Auth ${AUTH_USER}/***)`));