[object Object]

← back to build-pages

refactor: parseSince() helper — apply ?since= filter to /api/search + /api/projects/:slug/commits

4a5298c8530ebbc3021899a89a380248452acfe8 · 2026-05-13 18:34:39 -0700 · SteveStudio2

Files touched

Diff

commit 4a5298c8530ebbc3021899a89a380248452acfe8
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 18:34:39 2026 -0700

    refactor: parseSince() helper — apply ?since= filter to /api/search + /api/projects/:slug/commits
---
 server.js | 50 +++++++++++++++++++++++++++++---------------------
 1 file changed, 29 insertions(+), 21 deletions(-)

diff --git a/server.js b/server.js
index 0177fc5..08a72b4 100644
--- a/server.js
+++ b/server.js
@@ -768,23 +768,24 @@ for (const facet of ['agents', 'skills']) {
 // /api/recent — most recent N commits across the entire fleet, merged + sorted
 // by date desc. Powers a "today across the fleet" rail. Shares the bundle
 // cache so it's free after the first warm.
+// Parse a ?since= query value into a unix ms cutoff (or 0 = no filter).
+// Accepts 24h, 7d, 30m, or an ISO date prefix (YYYY-MM-DD…). Returns 0 for
+// missing/malformed input so callers can use the result as a min-Date filter
+// without separate "is it set" checks.
+function parseSince(s) {
+  if (typeof s !== 'string' || !s.length) return 0;
+  const rel = s.match(/^(\d+)([dhm])$/);
+  if (rel) {
+    const mult = rel[2] === 'd' ? 86400000 : rel[2] === 'h' ? 3600000 : 60000;
+    return Date.now() - parseInt(rel[1], 10) * mult;
+  }
+  if (/^\d{4}-\d{2}-\d{2}/.test(s)) return new Date(s).getTime();
+  return 0;
+}
+
 app.get('/api/recent', async (req, res, next) => {
   const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 20, 1), 200);
-  // Optional ?since=YYYY-MM-DD or ?since=1d|7d|24h date filter — anything older
-  // than the cutoff is dropped before the merge. Lets you slice "what shipped
-  // since last Monday" without paginating through everything.
-  let sinceMs = 0;
-  if (typeof req.query.since === 'string' && req.query.since.length) {
-    const s = req.query.since;
-    const rel = s.match(/^(\d+)([dhm])$/);
-    if (rel) {
-      const [, n, u] = rel;
-      const mult = u === 'd' ? 86400000 : u === 'h' ? 3600000 : 60000;
-      sinceMs = Date.now() - parseInt(n, 10) * mult;
-    } else if (/^\d{4}-\d{2}-\d{2}/.test(s)) {
-      sinceMs = new Date(s).getTime();
-    }
-  }
+  const sinceMs = parseSince(req.query.since);
   try {
     const all = [];
     for (const [slug, p] of Object.entries(PROJECTS)) {
@@ -816,6 +817,7 @@ app.get('/api/recent', async (req, res, next) => {
 app.get('/api/search', async (req, res, next) => {
   const q = (typeof req.query.q === 'string' ? req.query.q : '').trim().toLowerCase();
   const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
+  const sinceMs = parseSince(req.query.since);
   if (q.length < 2) return res.json({ q, results: [], note: 'min 2 chars' });
   try {
     // Phase 1 — pull ALL matches per project (no cap), so the "one project
@@ -824,9 +826,10 @@ app.get('/api/search', async (req, res, next) => {
       Object.entries(PROJECTS).map(async ([slug, p]) => {
         const bundle = await getProjectBundle(slug, p).catch(() => null);
         if (!bundle) return { slug, name: p.name, hits: [] };
-        const hits = bundle.commits.filter(c =>
-          (c.subject + ' ' + c.body).toLowerCase().includes(q)
-        ).map(c => ({
+        const hits = bundle.commits.filter(c => {
+          if (sinceMs && new Date(c.dateISO).getTime() < sinceMs) return false;
+          return (c.subject + ' ' + c.body).toLowerCase().includes(q);
+        }).map(c => ({
           slug, project: p.name, hash: c.shortHash, date: c.dateISO.slice(0, 10), subject: c.subject,
         }));
         return { slug, name: p.name, hits };
@@ -881,12 +884,17 @@ app.get('/api/projects/:slug/commits', async (req, res, next) => {
     const { commits } = await getProjectBundle(req.params.slug, p);
     const limit  = Math.min(Math.max(parseInt(req.query.limit,  10) || 100, 1), 500);
     const offset = Math.max(parseInt(req.query.offset, 10) || 0, 0);
+    const sinceMs = parseSince(req.query.since);
+    const filtered = sinceMs
+      ? commits.filter(c => new Date(c.dateISO).getTime() >= sinceMs)
+      : commits;
     res.setHeader('Cache-Control', 'public, max-age=30, stale-while-revalidate=60');
     res.json({
       slug: req.params.slug,
-      total: commits.length,
+      total: filtered.length,
       limit, offset,
-      commits: commits.slice(offset, offset + limit).map(c => ({
+      since: sinceMs ? new Date(sinceMs).toISOString() : null,
+      commits: filtered.slice(offset, offset + limit).map(c => ({
         hash: c.shortHash, date: c.dateISO, author: c.author, subject: c.subject, body: c.body,
       })),
     });
@@ -1006,7 +1014,7 @@ app.get('/api', (_req, res) => {
       'GET /api/projects/:slug/agents':        'Raw agents facet — sub-agent names auto-extracted from commit bodies',
       'GET /api/projects/:slug/skills':        'Raw skills facet — /skill-name slugs auto-extracted from commit bodies',
       'GET /api/projects/:slug/ideas':         'Raw ideas — commit bodies ≥120 chars (design-rationale + scaffolds)',
-      'GET /api/search':                       'Cross-project commit search — ?q=<term>&limit=N, round-robin + by_project breakdown',
+      'GET /api/search':                       'Cross-project commit search — ?q=<term>&limit=N&since=<24h|7d|YYYY-MM-DD>, round-robin + by_project breakdown',
       'GET /api/recent':                       'Most recent N commits across the whole fleet — date-sorted, ?limit=20&since=<24h|7d|YYYY-MM-DD>',
       'GET /api/fleet/agents':                 'Fleet-wide agent tallies merged across every project',
       'GET /api/fleet/skills':                 'Fleet-wide skill tallies merged across every project',

← d965741 api/recent: ?since= filter (24h|7d|YYYY-MM-DD) + 2 smoke  ·  back to build-pages  ·  smoke: 3 ?since= assertions across /api/search + /api/projec 035207b →