[object Object]

← 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 →