[object Object]

← back to Wallco Ai

wallco.ai · /api/designs/search?q=&limit= — lightweight in-memory search across catalog scoring motif(5 exact/3 contains), title(2), category(2 exact/1 contains), handle(1); top-N by score then saturation; rejects q<2 chars; emits Last-Modified+Cache-Control public 5min so crawlers + clients can cache search results; verified rose+floral → 81 hits with Rose Bouquet No.161 (score 14) leading, damask → 180 hits

7d5cb786f3fa0c10dd3e0c449a40db72b34e657a · 2026-05-12 07:52:21 -0700 · SteveStudio2

Files touched

Diff

commit 7d5cb786f3fa0c10dd3e0c449a40db72b34e657a
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 07:52:21 2026 -0700

    wallco.ai · /api/designs/search?q=&limit= — lightweight in-memory search across catalog scoring motif(5 exact/3 contains), title(2), category(2 exact/1 contains), handle(1); top-N by score then saturation; rejects q<2 chars; emits Last-Modified+Cache-Control public 5min so crawlers + clients can cache search results; verified rose+floral → 81 hits with Rose Bouquet No.161 (score 14) leading, damask → 180 hits
---
 server.js | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/server.js b/server.js
index 3ecd8c2..34b2b51 100644
--- a/server.js
+++ b/server.js
@@ -216,6 +216,59 @@ function maybe304(req, res, lastModifiedDate) {
   return false;
 }
 
+// ── Lightweight in-memory search across the catalog.
+// /api/designs/search?q=<term>&limit=20
+// Scores each design on motif (3), title (2), category/handle (1) substring match.
+// Returns top-N by score then by saturation. Pure read-only, no DB.
+app.get('/api/designs/search', (req, res) => {
+  const qRaw = String(req.query.q || '').toLowerCase().trim();
+  const limit = Math.max(1, Math.min(50, parseInt(req.query.limit || '20', 10)));
+  if (!qRaw || qRaw.length < 2) {
+    return res.json({ q: qRaw, limit, count: 0, results: [], note: 'q must be ≥2 chars' });
+  }
+  // Conditional GET on the catalog freshness so crawlers benefit.
+  if (maybe304(req, res, CATALOG_LAST_MODIFIED)) return;
+  res.setHeader('Cache-Control', 'public, max-age=300, must-revalidate');
+  const terms = qRaw.split(/\s+/).filter(Boolean);
+  const out = [];
+  for (const d of DESIGNS) {
+    const title = (d.title || '').toLowerCase();
+    const category = (d.category || '').toLowerCase();
+    const handle = (d.handle || '').toLowerCase();
+    const motifs = (d.motifs || []).map(m => m.toLowerCase());
+    let score = 0;
+    for (const t of terms) {
+      if (motifs.some(m => m === t))      score += 5;     // exact motif match — strongest
+      else if (motifs.some(m => m.includes(t))) score += 3;
+      if (title.includes(t))    score += 2;
+      if (category === t)       score += 2;
+      else if (category.includes(t)) score += 1;
+      if (handle.includes(t))   score += 1;
+    }
+    if (score > 0) {
+      out.push({
+        id: d.id,
+        title: d.title,
+        category: d.category,
+        handle: d.handle,
+        image_url: d.image_url,
+        dominant_hex: d.dominant_hex,
+        room_mockups: d.room_mockups || [],
+        score,
+        saturation: d.saturation || 0,
+        url: `/design/${d.id}`,
+      });
+    }
+  }
+  out.sort((a, b) => b.score - a.score || b.saturation - a.saturation);
+  res.json({
+    q: qRaw,
+    limit,
+    count: out.length,
+    results: out.slice(0, limit),
+  });
+});
+
 // ── Color sort helpers
 function hexToHSL(hex) {
   if (!hex) return [0, 0, 0.5];

← d6cdb24 wallco.ai · all target=_blank now rel=noopener noreferrer (t  ·  back to Wallco Ai  ·  wallco.ai · /designs search box → live autocomplete dropdown 3622bf5 →