← back to 4square Agentabrams
/api/all-hero-grids: discover heroes on disk (not just wired) — surfaces all 52 DW sites + honors tossed-domains list
bc6abcddd29a8a7814dc1032849a086faf9a176f · 2026-05-14 10:28:57 -0700 · Steve
Files touched
Diff
commit bc6abcddd29a8a7814dc1032849a086faf9a176f
Author: Steve <steve@designerwallcoverings.com>
Date: Thu May 14 10:28:57 2026 -0700
/api/all-hero-grids: discover heroes on disk (not just wired) — surfaces all 52 DW sites + honors tossed-domains list
---
server.js | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 93 insertions(+), 22 deletions(-)
diff --git a/server.js b/server.js
index 651df95..f5c13f1 100644
--- a/server.js
+++ b/server.js
@@ -236,30 +236,101 @@ app.post('/api/site/:domain/hero-grid', express.json({ limit: '2mb' }), (req, re
}
});
-// ── /api/all-hero-grids ── batch read every wired site's hero-4grid.json
-// Returns [{ domain, kind, grid }] for every site in wiredSites. Synthesizes
-// a default grid from /api/products when a site has no hero-4grid.json yet.
+// ── /api/all-hero-grids ── discovers every hero-4grid.json on disk and
+// returns one entry per site. Sources scanned (in order):
+// 1. /var/www/*.com/public/hero-4grid.json (Kamatera prod layout)
+// 2. /root/Projects/*/public/hero-4grid.json (the 9 dynamic DW sites)
+// 3. ~/Projects/*/public/hero-4grid.json (Mac2 local dev fallback)
+// This is broader than wiredSites — wiredSites tracks which sites have a
+// product API for drag-publish; all_hero_grids tracks any site that has a
+// 2×2 hero JSON deployed, even if drag-publish isn't wired yet.
app.get('/api/all-hero-grids', async (req, res) => {
- const out = [];
- const domains = Object.keys(wiredSites);
- // Read in parallel — capped at 8 concurrent file reads to be polite.
- const conc = 8;
- for (let i = 0; i < domains.length; i += conc) {
- const batch = domains.slice(i, i + conc);
- const results = await Promise.all(batch.map(async (domain) => {
- const gp = heroGridPath(domain);
- // Pull site metadata (kind, name) from sites.json
- const site = ((JSON.parse(fs.readFileSync(sitesPath, 'utf8')).sites || []).find(s => s.domain === domain)) || { domain, kind: 'unknown' };
- try {
- const buf = fs.readFileSync(gp, 'utf8');
- return { domain, kind: site.kind, name: site.name || domain, grid: JSON.parse(buf), source: 'file' };
- } catch (e) {
- // File missing or unreadable — return empty grid placeholder
- return { domain, kind: site.kind, name: site.name || domain, grid: { cells: [null,null,null,null], rotation_pool: [] }, source: 'missing' };
- }
- }));
- out.push(...results);
+ const seen = new Map(); // domain → { domain, kind, name, grid, source }
+ const tossedPath = path.join(process.env.HOME || '/root', 'Projects', '_shared', 'data', 'dw-tossed-domains.json');
+ let tossed = new Set();
+ try {
+ const tj = JSON.parse(fs.readFileSync(tossedPath, 'utf8'));
+ tossed = new Set((tj.tossed || []).map(t => t.domain));
+ } catch (e) {
+ // Try the prod path
+ try {
+ const tj2 = JSON.parse(fs.readFileSync('/root/Projects/_shared/data/dw-tossed-domains.json', 'utf8'));
+ tossed = new Set((tj2.tossed || []).map(t => t.domain));
+ } catch (e2) { /* none */ }
+ }
+
+ // Site metadata lookup from sites.json (kind, name)
+ let sitesMeta = {};
+ try {
+ const sjson = JSON.parse(fs.readFileSync(sitesPath, 'utf8'));
+ for (const s of (sjson.sites || [])) sitesMeta[s.domain] = s;
+ } catch (e) { /* no metadata */ }
+
+ function tryAdd(domain, gridFile) {
+ if (seen.has(domain) || tossed.has(domain)) return;
+ try {
+ const buf = fs.readFileSync(gridFile, 'utf8');
+ const grid = JSON.parse(buf);
+ const meta = sitesMeta[domain] || {};
+ seen.set(domain, {
+ domain,
+ kind: meta.kind || (domain.endsWith('.com') ? 'wallcovering' : 'other'),
+ name: meta.name || domain,
+ grid,
+ source: 'file',
+ wired: !!(meta.source && meta.source.endpoint),
+ path: gridFile,
+ });
+ } catch (e) { /* skip unreadable */ }
+ }
+
+ // 1. /var/www/<domain>/public/hero-4grid.json
+ try {
+ const wwwRoots = fs.readdirSync('/var/www', { withFileTypes: true });
+ for (const e of wwwRoots) {
+ if (!e.isDirectory()) continue;
+ if (!/^[a-z0-9-]+\.[a-z.]+$/i.test(e.name)) continue;
+ tryAdd(e.name, '/var/www/' + e.name + '/public/hero-4grid.json');
+ }
+ } catch (e) { /* no /var/www on Mac2 */ }
+
+ // 2. /root/Projects/<site>/public/hero-4grid.json
+ try {
+ const rootProj = fs.readdirSync('/root/Projects', { withFileTypes: true });
+ for (const e of rootProj) {
+ if (!e.isDirectory()) continue;
+ const dom = e.name + '.com';
+ tryAdd(dom, '/root/Projects/' + e.name + '/public/hero-4grid.json');
+ }
+ } catch (e) { /* no /root/Projects on Mac2 */ }
+
+ // 3. ~/Projects/<site>/public/hero-4grid.json (Mac2 dev)
+ try {
+ const homeProj = path.join(process.env.HOME || '/root', 'Projects');
+ const localProj = fs.readdirSync(homeProj, { withFileTypes: true });
+ for (const e of localProj) {
+ if (!e.isDirectory()) continue;
+ const dom = e.name + '.com';
+ tryAdd(dom, path.join(homeProj, e.name, 'public', 'hero-4grid.json'));
+ }
+ } catch (e) { /* ignore */ }
+
+ // ALSO include any wiredSites that DON'T have a hero-4grid yet, so the
+ // wall shows them as "needs hero" rather than hiding them entirely.
+ for (const dom of Object.keys(wiredSites)) {
+ if (seen.has(dom) || tossed.has(dom)) continue;
+ const meta = sitesMeta[dom] || {};
+ seen.set(dom, {
+ domain: dom,
+ kind: meta.kind || 'unknown',
+ name: meta.name || dom,
+ grid: { cells: [null,null,null,null], rotation_pool: [] },
+ source: 'missing',
+ wired: true,
+ });
}
+
+ const out = Array.from(seen.values());
res.json({ ok: true, count: out.length, sites: out, ts: Date.now() });
});
← d0bb87b 4Square: + /wall — contact-sheet view of every site's 2×2 he
·
back to 4square Agentabrams
·
add rel=noreferrer to external target=_blank links 75729fd →