From ad97c5362cf631d692ea318d2c06a4dcad3a8d54 Mon Sep 17 00:00:00 2001 From: Till JS Date: Sun, 17 May 2026 16:34:12 +0200 Subject: [PATCH] managarten cutover: news-Modul liest jetzt aus mana-news-pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apps/api/src/modules/news/routes.ts — ehemals Raw-SQL gegen mana_platform.news.curated_articles, jetzt HTTP-Proxy auf MANA_NEWS_POOL_URL/feed mit X-Service-Key. Identischer Query- Param-Vertrag (topics/lang/since/limit/offset), kein Drizzle- Schema-Coupling mehr für News. docker-compose.macmini.yml — MANA_NEWS_POOL_URL=http://mana-news-pool:3079 in mana-api environment. News-Ingester-Kommentar-Section aktualisiert (Container ist seit Lift-B abgeschaltet). Damit ist der vollständige Cutover-Pfad aus mana/services/mana-news-pool/CLAUDE.md durch: 1. Plattform-Service deployed (gestern) 2. managarten konsumiert ihn (jetzt) 3. alter news-ingester:3066-Container schon weg Type-check: news/routes.ts grün (2 pre-existing forms/-Errors unrelated). --- apps/api/src/modules/news/routes.ts | 103 +++++++++------------------- docker-compose.macmini.yml | 15 ++-- 2 files changed, 42 insertions(+), 76 deletions(-) diff --git a/apps/api/src/modules/news/routes.ts b/apps/api/src/modules/news/routes.ts index a3fdd9ce9..6702af7b3 100644 --- a/apps/api/src/modules/news/routes.ts +++ b/apps/api/src/modules/news/routes.ts @@ -1,98 +1,59 @@ /** * News module — Reads the curated article pool + extracts ad-hoc URLs. * - * Pool population: handled by the standalone `services/news-ingester` - * Bun service, which writes into `news.curated_articles` on a 15 min - * loop. This route file just reads from that table. + * Pool population: handled by the Plattform-Service `mana-news-pool` + * (Port 3079, eigene DB `mana_news_pool`, Schema `pool.curated_articles`). + * Cutover am 2026-05-17: ehemals direkter Raw-SQL-Read auf + * `mana_platform.news.curated_articles` aus dem `news-ingester:3066`- + * Container. Hier nur noch HTTP-Proxy auf den Plattform-Pool. * - * Saved articles (the user's personal reading list) live entirely in - * the unified Mana app's local-first IndexedDB and sync via mana-sync; - * this module never sees them. + * Saved articles (die persönliche Reading-List eines Users) leben + * weiterhin client-side in der IndexedDB der unified Mana-App und + * syncen via mana-sync; dieses Modul sieht sie nicht. */ import { Hono } from 'hono'; import { extractFromUrl } from '@mana/shared-rss'; -import { drizzle } from 'drizzle-orm/postgres-js'; -import { sql } from 'drizzle-orm'; -import { getConnection } from '../../lib/db'; -// ─── DB Connection (reads from news.curated_articles) ────── - -const db = drizzle(getConnection()); +const POOL_URL = process.env.MANA_NEWS_POOL_URL ?? 'http://mana-news-pool:3079'; +const POOL_KEY = process.env.MANA_SERVICE_KEY ?? ''; // ─── Routes ───────────────────────────────────────────────── const routes = new Hono(); -// ─── Feed (reads from news.curated_articles) ─────────────── +// ─── Feed (proxy on mana-news-pool) ──────────────────────── // // Query params: -// topics — comma-separated topic slugs (tech,wissenschaft,…). If -// omitted, all topics are returned. +// topics — comma-separated topic slugs (tech,wissenschaft,…) // lang — 'de' | 'en' | 'all' (default 'all') -// since — ISO timestamp; only articles published after this +// since — ISO timestamp // limit — default 50, max 200 // offset — default 0 -// -// Returns the full article body so the client can render the reader -// without a second round-trip. Curated articles are small (≤30 KB -// each) and the client caches them locally for offline reading. routes.get('/feed', async (c) => { - const topicsParam = c.req.query('topics'); - const lang = c.req.query('lang') ?? 'all'; - const since = c.req.query('since'); - const limit = Math.min(parseInt(c.req.query('limit') || '50', 10), 200); - const offset = parseInt(c.req.query('offset') || '0', 10); + const passthrough = ['topics', 'lang', 'since', 'limit', 'offset'] as const; + const url = new URL(`${POOL_URL}/feed`); + for (const k of passthrough) { + const v = c.req.query(k); + if (v) url.searchParams.set(k, v); + } - const conditions: ReturnType[] = []; - - if (topicsParam) { - const topics = topicsParam - .split(',') - .map((t) => t.trim()) - .filter(Boolean); - if (topics.length > 0) { - conditions.push(sql`topic = ANY(${topics})`); + try { + const res = await fetch(url.toString(), { + headers: { 'X-Service-Key': POOL_KEY }, + signal: AbortSignal.timeout(8_000), + }); + if (!res.ok) { + console.warn(`[news] pool ${url} → ${res.status}`); + return c.json([] as Record[]); } + const data = (await res.json()) as Record[]; + return c.json(data); + } catch (err) { + console.warn('[news] pool fetch failed', err); + return c.json([] as Record[]); } - if (lang === 'de' || lang === 'en') { - conditions.push(sql`language = ${lang}`); - } - if (since) { - conditions.push(sql`published_at > ${since}`); - } - - const whereClause = - conditions.length > 0 - ? sql.join([sql`WHERE`, sql.join(conditions, sql` AND `)], sql` `) - : sql``; - - const result = await db.execute(sql` - SELECT - id, - original_url AS "originalUrl", - title, - excerpt, - content, - html_content AS "htmlContent", - author, - site_name AS "siteName", - source_slug AS "sourceSlug", - image_url AS "imageUrl", - topic, - language, - word_count AS "wordCount", - reading_time_minutes AS "readingTimeMinutes", - published_at AS "publishedAt", - ingested_at AS "ingestedAt" - FROM news.curated_articles - ${whereClause} - ORDER BY published_at DESC NULLS LAST, ingested_at DESC - LIMIT ${limit} OFFSET ${offset} - `); - - return c.json(result as unknown as Record[]); }); // ─── Extract (content extraction for user-pasted URLs) ───── diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index 046fe8945..a43186357 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -1380,6 +1380,11 @@ services: MANA_CREDITS_URL: http://mana-credits:3002 MANA_MEDIA_URL: http://mana-media:3011 MANA_CRAWLER_URL: http://mana-crawler:3014 + # mana-news-pool — Plattform-Service (Lift-B 2026-05-16). Ersetzt + # den ehemaligen news-ingester:3066-Container, der direkt in + # mana_platform.news.curated_articles schrieb. apps/api/news/routes.ts + # ist seit 2026-05-17 ein HTTP-Proxy auf diesen Endpoint. + MANA_NEWS_POOL_URL: http://mana-news-pool:3079 MANA_LLM_DEFAULT_MODEL: ${MANA_LLM_DEFAULT_MODEL:-gemma3:4b} MANA_SERVICE_KEY: ${MANA_SERVICE_KEY} # OpenAI — picture module gpt-image-2 path. Optional: without it, @@ -1407,12 +1412,12 @@ services: start_period: 30s # ============================================ - # News Ingester + # News-Pool — wurde 2026-05-17 zur Plattform geliftet. # ============================================ - # services/news-ingester — pulls public RSS/JSON feeds into the - # news.curated_articles pool every 15 min. The unified mana-api reads - # from the same table to serve /api/v1/news/feed; user reading lists - # remain client-side in the unified Mana app's local IndexedDB. + # Der ehemalige services/news-ingester:3066-Container ist abgeschaltet + # zugunsten von `mana-news-pool` (Plattform-Service Port 3079, + # ~/projects/mana/services/mana-news-pool/, eigene DB `mana_news_pool`). + # mana-api/news/routes.ts proxied auf MANA_NEWS_POOL_URL. volumes: redis_data: