[object Object]

← back to Stars of Design

API: add /api/{designers,firms,clients}/featured (parity with asim featured)

deb329fc26342dc6e2641e56972da9ffc94f491c · 2026-05-13 09:46:43 -0700 · Steve Abrams

Files touched

Diff

commit deb329fc26342dc6e2641e56972da9ffc94f491c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 13 09:46:43 2026 -0700

    API: add /api/{designers,firms,clients}/featured (parity with asim featured)
---
 routes/public.js | 45 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/routes/public.js b/routes/public.js
index 9a5826a..46f1f49 100644
--- a/routes/public.js
+++ b/routes/public.js
@@ -404,6 +404,9 @@ router.get('/api', (_req, res) => {
       'GET /api/designers/random':       'Random designer — JSON {slug, display_name}',
       'GET /api/firms/random':           'Random firm — JSON {slug, name}',
       'GET /api/clients/random':         'Random DW client — JSON {slug, name}',
+      'GET /api/designers/featured':     'N random designers (?n=6, max 24) — multi-row companion to /api/designers/random',
+      'GET /api/firms/featured':         'N random firms (?n=6, max 24)',
+      'GET /api/clients/featured':       'N random DW clients (?n=6, max 24)',
       'GET /random/designer':            '302 redirect to a random designer detail page',
       'GET /random/firm':                '302 redirect to a random firm detail page',
       'GET /random/client':              '302 redirect to a random DW client detail page',
@@ -467,6 +470,48 @@ router.get('/api/stats', async (_req, res, next) => {
 // (parity with asim) and by humans hitting "feeling lucky"-style UX.
 // 30s/60s SWR — cache lifespan is short by design so the random feel
 // stays fresh while keeping pg hits cheap under repeat polling.
+// /api/{designers,firms,clients}/featured — N (default 6, max 24) random rows.
+// Used by widgets that want a small grid inline. 60s cache + 120s SWR.
+function pickN(arr, n) {
+  if (arr.length <= n) return arr.slice();
+  const out = [];
+  const seen = new Set();
+  while (out.length < n) {
+    const i = Math.floor(Math.random() * arr.length);
+    if (seen.has(i)) continue;
+    seen.add(i);
+    out.push(arr[i]);
+  }
+  return out;
+}
+router.get('/api/designers/featured', (req, res, next) => {
+  try {
+    const n = Math.min(Math.max(parseInt(req.query.n, 10) || 6, 1), 24);
+    const all = data.load();
+    const picks = pickN(all, n).map((d) => ({ slug: d.slug, display_name: d.name || d.full_name || null }));
+    res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=120');
+    res.json({ designers: picks });
+  } catch (e) { next(e); }
+});
+router.get('/api/firms/featured', async (req, res, next) => {
+  try {
+    const n = Math.min(Math.max(parseInt(req.query.n, 10) || 6, 1), 24);
+    const rows = await firmsLib.listFirms({ limit: 1000 });
+    const picks = pickN(rows, n).map((f) => ({ slug: f.slug, name: f.name || null }));
+    res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=120');
+    res.json({ firms: picks });
+  } catch (e) { next(e); }
+});
+router.get('/api/clients/featured', async (req, res, next) => {
+  try {
+    const n = Math.min(Math.max(parseInt(req.query.n, 10) || 6, 1), 24);
+    const rows = await clientsLib.listClients({ limit: 1000 });
+    const picks = pickN(rows, n).map((c) => ({ slug: c.slug, name: c.full_name || c.name || null }));
+    res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=120');
+    res.json({ clients: picks });
+  } catch (e) { next(e); }
+});
+
 router.get('/api/designers/random', (_req, res, next) => {
   try {
     const all = data.load();

← ce39c6b sitemap.txt: emit Last-Modified header for conditional GET (  ·  back to Stars of Design  ·  smoke: 6 new checks for /api/{designers,firms,clients}/featu 3774c03 →