daily-briefing/server/routers/news.py
Sam d3305a243c Weather: Replace wttr.in with Open-Meteo + structured logging
- Replace wttr.in (unreachable from Docker) with Open-Meteo API
  (free, no API key, reliable) with geocoding cache
- WMO weather codes mapped to German descriptions + emoji icons
- Add [WEATHER], [NEWS], [UNRAID], [DASHBOARD] log prefixes
- Structured integration status table on startup
- Suppress noisy httpx INFO logs (services log their own summaries)
- Add logging to unraid_service (was completely silent on errors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:45:23 +01:00

83 lines
2.4 KiB
Python

"""News articles router -- paginated, filterable by category."""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Query
from server.cache import cache
from server.config import get_settings
from server.services.news_service import get_news, get_news_count
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api", tags=["news"])
def _cache_key(limit: int, offset: int, category: Optional[str]) -> str:
return f"news:{limit}:{offset}:{category}"
@router.get("/news")
async def get_news_articles(
limit: int = Query(default=20, le=50, ge=1),
offset: int = Query(default=0, ge=0),
category: Optional[str] = Query(default=None),
) -> Dict[str, Any]:
"""Return a paginated list of news articles.
Response shape::
{
"articles": [ ... ],
"total": int,
"limit": int,
"offset": int,
}
"""
key = _cache_key(limit, offset, category)
# --- cache hit? -----------------------------------------------------------
cached = await cache.get(key)
if cached is not None:
logger.debug("[NEWS] Cache hit (key=%s)", key)
return cached
# --- cache miss -----------------------------------------------------------
articles: List[Dict[str, Any]] = []
total: int = 0
try:
articles = await get_news(limit=limit, offset=offset, category=category, max_age_hours=get_settings().news_max_age_hours)
logger.info("[NEWS] Fetched %d articles (limit=%d, offset=%d, category=%s)",
len(articles), limit, offset, category)
except Exception as exc:
logger.exception("[NEWS] Failed to fetch articles")
return {
"articles": [],
"total": 0,
"limit": limit,
"offset": offset,
"error": True,
"message": str(exc),
}
try:
total = await get_news_count(max_age_hours=get_settings().news_max_age_hours, category=category)
except Exception as exc:
logger.exception("Failed to fetch news count")
# We still have articles -- return them with total = len(articles)
total = len(articles)
payload: Dict[str, Any] = {
"articles": articles,
"total": total,
"limit": limit,
"offset": offset,
}
await cache.set(key, payload, get_settings().news_cache_ttl)
return payload