#!/usr/bin/env python3 """ News Router GET /api/news, GET /api/news/latest """ from fastapi import APIRouter, HTTPException, Query from fastapi.responses import JSONResponse from typing import Optional, List, Dict, Any from datetime import datetime, timezone import logging import json from pathlib import Path from backend.services.provider_fallback_manager import fallback_manager from backend.services.data_resolver import get_data_resolver from backend.services.persistence_service import PersistenceService logger = logging.getLogger(__name__) router = APIRouter( prefix="/api/news", tags=["News"] ) persistence_service = PersistenceService() WORKSPACE_ROOT = Path("/app" if Path("/app").exists() else Path(".")) RESOURCES_JSON = WORKSPACE_ROOT / "api-resources" / "crypto_resources_unified_2025-11-11.json" def create_response(data: Any, source: str, attempted: List[str] = None) -> Dict[str, Any]: """Create standardized response""" return { "data": data, "meta": { "source": source, "generated_at": datetime.now(timezone.utc).isoformat(), "cache_ttl": 300, "attempted": attempted or [source] } } @router.get("") async def get_news( limit: int = Query(20, description="Number of articles"), symbol: Optional[str] = Query(None, description="Filter by symbol") ): """Get cryptocurrency news""" attempted = [] # Try HF Space first try: resolver = await get_data_resolver() news = await resolver.resolve_news(limit=limit, symbol=symbol) if news: attempted.append("hf") # Save to database await persistence_service.save_news_items(news) return create_response(news, "hf", attempted) except Exception as e: logger.warning(f"HF Space unavailable: {e}") attempted.append("hf") # Try local resources try: if RESOURCES_JSON.exists(): with open(RESOURCES_JSON, 'r', encoding='utf-8') as f: resources = json.load(f) news_sources = resources.get("registry", {}).get("news", []) if news_sources: attempted.append("local_resources") # Return sample news structure sample_news = [ { "title": f"News from {source.get('name', 'Unknown')}", "source": source.get("name", "Unknown"), "url": source.get("base_url", ""), "published_at": datetime.now(timezone.utc).isoformat() } for source in news_sources[:limit] ] return create_response(sample_news, "local_resources", attempted) except Exception as e: logger.warning(f"Local resources failed: {e}") attempted.append("local_resources") # Fallback to external APIs try: result = await fallback_manager.fetch_with_fallback( endpoint="/everything", params={"q": symbol or "cryptocurrency", "pageSize": limit}, transform_func=lambda x: x.get("articles", []) if isinstance(x, dict) else [] ) attempted.extend(result.attempted) if result.success and result.data: await persistence_service.save_news_items(result.data) return create_response(result.data, result.source, attempted) except Exception as e: logger.error(f"External APIs failed: {e}") attempted.append("external_apis") # Return empty with attempted sources return create_response([], "none", attempted) @router.get("/latest") async def get_latest_news( symbol: str = Query("BTC", description="Cryptocurrency symbol"), limit: int = Query(10, description="Number of articles") ): """Get latest news for specific symbol""" return await get_news(limit=limit, symbol=symbol)