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