[object Object]

← back to AsSeenInMovies

api/spotted/featured: N (default 6, max 24) verified spotted placements with movie context

e3ecee03e5cea69d567075664ae17df455fe307c · 2026-05-13 10:10:54 -0700 · SteveStudio2

Files touched

Diff

commit e3ecee03e5cea69d567075664ae17df455fe307c
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 10:10:54 2026 -0700

    api/spotted/featured: N (default 6, max 24) verified spotted placements with movie context
---
 server.js     | 19 +++++++++++++++++++
 test/smoke.js |  5 +++++
 2 files changed, 24 insertions(+)

diff --git a/server.js b/server.js
index 0b6cbd4..158bd5d 100644
--- a/server.js
+++ b/server.js
@@ -109,6 +109,7 @@ app.get('/api', (_req, res) => {
       'GET /api/spotted':        'Verified SPOTTED placements (limit ≤ 200, optional category=…)',
       'GET /api/spotted/stats':  'Aggregate counts (pending/live/rejected/submitted_7d/films/brands/categories)',
       'GET /api/spotted/random': 'A single random verified SPOTTED placement (with movie context)',
+      'GET /api/spotted/featured':'N random verified SPOTTED placements (?n=6, max 24) — multi-row companion',
       'GET /api/movies/random':  'A single random poster-having movie',
       'GET /api/people/random':  'A single random headshot-having person',
       'GET /api/credits/stats':  'Aggregate counts on asim_credits (total, acting, directing, writing, distinct_people, distinct_movies)',
@@ -823,6 +824,24 @@ app.get('/api/spotted/random', async (_req, res) => {
   }
 });
 
+// /api/spotted/featured — N (default 6, max 24) verified spotted rows with
+// movie context, randomly sampled. Multi-row companion to /api/spotted/random.
+// Used by widgets that want a small grid inline. 60s cache + 120s SWR.
+app.get('/api/spotted/featured', async (req, res) => {
+  try {
+    const n = Math.min(Math.max(parseInt(req.query.n, 10) || 6, 1), 24);
+    const r = await pool.query(`
+      SELECT s.id, s.product_name, s.product_category, s.brand,
+             s.scene_description, s.scene_timecode, s.shop_url,
+             m.id AS movie_id, m.title AS movie_title, m.release_year, m.poster_url
+        FROM asim_spotted s JOIN asim_movies m ON m.id = s.movie_id
+       WHERE s.verified_at IS NOT NULL AND (s.verified_by IS NULL OR s.verified_by NOT LIKE 'rejected%')
+       ORDER BY random() LIMIT $1`, [n]);
+    res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=120');
+    res.json({ spotted: r.rows });
+  } catch (e) { res.status(500).json({ error: e.message }); }
+});
+
 // /api/credits/stats — aggregate counts on asim_credits. Departments, actors
 // vs crew, top contributors. Cached 5 min (363k rows is heavy to recount).
 //
diff --git a/test/smoke.js b/test/smoke.js
index 3e1408c..0be75c3 100644
--- a/test/smoke.js
+++ b/test/smoke.js
@@ -134,6 +134,11 @@ function check(name, cond, hint = '') {
   check('api/spotted/random: 200', r.status === 200);
   check('api/spotted/random: spotted field', /"spotted":\{/.test(r.body));
 
+  // 11b2. /api/spotted/featured?n=3
+  r = await fetch('/api/spotted/featured?n=3');
+  check('api/spotted/featured: 200', r.status === 200);
+  check('api/spotted/featured: array', /"spotted":\[/.test(r.body));
+
   // 11c. /api/credits/stats
   r = await fetch('/api/credits/stats');
   check('api/credits/stats: 200', r.status === 200);

← d1d1f7a smoke: 4 assertions for /api/version (200, name, git sha, no  ·  back to AsSeenInMovies  ·  smoke: assert /api/version Cache-Control no-store db4fdf7 →