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