← back to Ventura Corridor
iter 148: /api/magazine/lottery?n=N&cat= — random N features for rotators/IG backup picks
a9953cdd6bdc694e81e8be2ef510c66ab52010a7 · 2026-05-06 19:14:56 -0700 · SteveStudio2
Files touched
Diff
commit a9953cdd6bdc694e81e8be2ef510c66ab52010a7
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 19:14:56 2026 -0700
iter 148: /api/magazine/lottery?n=N&cat= — random N features for rotators/IG backup picks
---
src/server/index.ts | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/src/server/index.ts b/src/server/index.ts
index dcb7bf2..281bb82 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1891,6 +1891,39 @@ app.get('/api/magazine/random', async (req, res) => {
}
});
+// Lottery — N random published features. Useful for "5 you may have missed" rails,
+// IG-autopost backup picks, and the fleet-tv rotator. Optional ?cat= filter; ?n= clamps to 1..50.
+app.get('/api/magazine/lottery', async (req, res) => {
+ try {
+ const cat = String(req.query.cat || '').trim() || null;
+ const n = Math.min(50, Math.max(1, parseInt(String(req.query.n || 5), 10) || 5));
+ const where: string[] = [`mf.status = 'published'`, `length(mf.editorial) > 200`];
+ const params: any[] = [];
+ if (cat) { params.push(cat); where.push(`mf.category_tag = $${params.length}`); }
+ params.push(n);
+ const r = await query(
+ `SELECT mf.id, mf.headline, mf.subhead, mf.pull_quote, mf.category_tag,
+ mf.published_at, COALESCE(mf.taps, 0) AS taps, COALESCE(mf.views, 0) AS views,
+ b.name AS biz_name, b.address AS biz_address, b.city
+ FROM magazine_features mf JOIN businesses b ON b.id = mf.business_id
+ WHERE ${where.join(' AND ')}
+ ORDER BY random() LIMIT $${params.length}`,
+ params
+ );
+ res.json({
+ count: r.rowCount,
+ cat,
+ features: r.rows.map((f: any) => ({
+ ...f,
+ permalink: `/magazine/${f.id}`,
+ share_card: `/share/${f.id}`,
+ })),
+ });
+ } catch (e: any) {
+ res.status(500).json({ error: e.message });
+ }
+});
+
// Per-feature TTS audio — /api/magazine/:id/audio.aiff
// Uses macOS `say` command. Caches by editorial-hash so we don't re-render every visit.
import { existsSync as _fs_exists, mkdirSync as _fs_mkdir, statSync as _fs_stat, createReadStream as _fs_stream } from 'node:fs';
← af14cb7 iter 147: /api/magazine.json now accepts ?cat= and ?limit= f
·
back to Ventura Corridor
·
iter 149: per-feature editor margin notes — collapsible "add f050969 →