[object Object]

← back to Customdigitalmurals

add /api/facets returning FILTERED counts (vendors + product_types); honors q + vendor query params

7905e4b5ffeb96633855c9bab7a95af31547c072 · 2026-05-25 21:01:33 -0700 · Steve Abrams

Replaces the all-counts behavior with FILTERED counts so chip-style facet UIs
reflect what selecting each option will actually return. Legacy /api/vendors
kept for back-compat. Vendor names remain admin-only metadata — must not be
rendered on customer-facing UI.

Files touched

Diff

commit 7905e4b5ffeb96633855c9bab7a95af31547c072
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 25 21:01:33 2026 -0700

    add /api/facets returning FILTERED counts (vendors + product_types); honors q + vendor query params
    
    Replaces the all-counts behavior with FILTERED counts so chip-style facet UIs
    reflect what selecting each option will actually return. Legacy /api/vendors
    kept for back-compat. Vendor names remain admin-only metadata — must not be
    rendered on customer-facing UI.
---
 server.js | 42 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index 028e54c..9d8863c 100644
--- a/server.js
+++ b/server.js
@@ -100,7 +100,8 @@ app.get('/api/products', (req, res) => {
   res.json({ total, pages, page, per, products: slice });
 });
 
-// Vendor list for filter dropdown
+// Vendor list (LEGACY — kept for back-compat; new code should use /api/facets).
+// Vendor names are admin-only metadata and must NOT render on customer-facing UI.
 app.get('/api/vendors', (_req, res) => {
   const counts = {};
   ALL.forEach(p => { counts[p.vendor] = (counts[p.vendor] || 0) + 1; });
@@ -110,6 +111,45 @@ app.get('/api/vendors', (_req, res) => {
   res.json(list);
 });
 
+// Facets — counts reflect the CURRENT FILTERED result set (q + vendor), so the
+// numbers shown next to each facet option match what will actually be returned
+// if the user selects it. NOTE: `vendors` here is admin-only metadata; never
+// render vendor names on customer-facing UI (DW rule).
+app.get('/api/facets', (req, res) => {
+  const q      = (req.query.q || '').toLowerCase().trim();
+  const vendor = (req.query.vendor || '').toLowerCase().trim();
+
+  let filtered = ALL;
+  if (q) {
+    filtered = filtered.filter(p =>
+      p.title.toLowerCase().includes(q) ||
+      (p.vendor || '').toLowerCase().includes(q) ||
+      (p.product_type || '').toLowerCase().includes(q) ||
+      (p.tags || []).some(t => t.toLowerCase().includes(q))
+    );
+  }
+  if (vendor) {
+    filtered = filtered.filter(p => (p.vendor || '').toLowerCase() === vendor);
+  }
+
+  const vendors = {};
+  const productTypes = {};
+  filtered.forEach(p => {
+    if (p.vendor) vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
+    if (p.product_type) productTypes[p.product_type] = (productTypes[p.product_type] || 0) + 1;
+  });
+
+  const toList = obj => Object.entries(obj)
+    .sort((a, b) => b[1] - a[1])
+    .map(([name, count]) => ({ name, count }));
+
+  res.json({
+    total: filtered.length,
+    vendors: toList(vendors),
+    product_types: toList(productTypes),
+  });
+});
+
 // Single product page
 app.get('/product/:handle', (req, res) => {
   const p = ALL.find(x => x.handle === req.params.handle);

← 27a4699 retag aesthetic from PG: 3663 rows updated  ·  back to Customdigitalmurals  ·  remove vendor names from customer UI — replace with 'Designe f60aec2 →