[object Object]

← back to Ventura Corridor

iter 157: /api/magazine/feature-of-the-month — manual cover wins, falls back to auto-pick heuristic

8fee7065de2045ded8dd6a92f7b5b29a67e35eaf · 2026-05-06 19:28:03 -0700 · SteveStudio2

Files touched

Diff

commit 8fee7065de2045ded8dd6a92f7b5b29a67e35eaf
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 6 19:28:03 2026 -0700

    iter 157: /api/magazine/feature-of-the-month — manual cover wins, falls back to auto-pick heuristic
---
 src/server/index.ts | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/src/server/index.ts b/src/server/index.ts
index 65c5072..279494f 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1833,6 +1833,41 @@ app.get('/api/magazine/feature-of-the-day', async (_req, res) => {
   }
 });
 
+// Feature of the month — returns the cover-pick if set; otherwise the same heuristic as auto-cover.
+app.get('/api/magazine/feature-of-the-month', async (_req, res) => {
+  try {
+    const month = new Date().toISOString().slice(0, 7);
+    // Manual cover pick wins.
+    let r = await query(`
+      SELECT mf.id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+             i.cover_caption, i.cover_kicker, i.cover_image_path,
+             b.name AS biz_name, b.address AS biz_address, b.city
+      FROM magazine_issues i
+      JOIN magazine_features mf ON mf.id = i.cover_feature_id
+      JOIN businesses b ON b.id = mf.business_id
+      WHERE i.issue_month = $1 AND mf.status = 'published'
+    `, [month]);
+    if (r.rowCount > 0) return res.json({ feature: r.rows[0], source: 'cover_pick', month });
+
+    // Fallback: top-tap × 3 + views, this month, same heuristic as auto_cover.ts.
+    r = await query(`
+      SELECT mf.id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+             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 mf.status = 'published' AND length(mf.editorial) > 240
+        AND to_char(mf.published_at AT TIME ZONE 'America/Los_Angeles', 'YYYY-MM') = $1
+      ORDER BY (COALESCE(mf.taps,0) * 3 + COALESCE(mf.views,0)) DESC, mf.published_at DESC
+      LIMIT 1
+    `, [month]);
+    if (r.rowCount > 0) return res.json({ feature: r.rows[0], source: 'auto_pick', month });
+
+    return res.json({ feature: null, source: 'none', month });
+  } catch (e: any) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 // Unified daily payload — what an IG-autopost / morning-email / standup-audio job needs in one call.
 app.get('/api/magazine/today', async (_req, res) => {
   try {

← 37c543b iter 156: /api/magazine/quality-flags audit + nav-pill on to  ·  back to Ventura Corridor  ·  iter 158: coverage_snapshots table + nightly 11:55 PM job + 52e164c →