"""FastAPI backend for DS Task AI News""" from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List, Dict, Any, Optional import uvicorn from config import settings from news_fetcher import NewsFetcher from recommender import NewsRecommender # Initialize FastAPI app app = FastAPI( title="DS Task AI News API", description="AI-powered news retrieval and recommendation system", version="1.0.0" ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # In production, specify actual origins allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Initialize components news_fetcher = NewsFetcher() recommender = NewsRecommender() # Pydantic models class NewsQuery(BaseModel): query: str top_k: int = 5 class InterestsQuery(BaseModel): interests: List[str] top_k: int = 10 class SearchQuery(BaseModel): query: str source: Optional[str] = None top_k: int = 10 # API Endpoints @app.get("/") async def root(): """Health check endpoint""" return { "message": "DS Task AI News API is running!", "version": "1.0.0", "status": "healthy" } @app.get("/health") async def health_check(): """Detailed health check""" stats = recommender.get_store_stats() return { "status": "healthy", "vector_store": stats, "settings": { "embedding_model": settings.embedding_model, "vector_db_type": settings.vector_db_type, "rss_feeds_count": len(settings.rss_feeds) } } @app.post("/fetch-news") async def fetch_news(): """Fetch news from RSS feeds and add to vector store""" try: # Fetch news articles result = news_fetcher.fetch_and_save_news() if not result["success"]: raise HTTPException(status_code=500, detail=result.get("message", "Failed to fetch news")) # Add articles to vector store articles = result["articles"] store_result = recommender.add_articles_to_store(articles) if not store_result["success"]: raise HTTPException(status_code=500, detail=store_result.get("message", "Failed to add articles to store")) return { "success": True, "message": "News fetched and processed successfully", "articles_fetched": result["articles_count"], "articles_stored": store_result["articles_added"], "total_articles": store_result["total_articles"] } except Exception as e: raise HTTPException(status_code=500, detail=f"Error fetching news: {str(e)}") @app.get("/recommend-news") async def recommend_news( article_id: str = Query(..., description="ID of the article to find similar articles for"), top_k: int = Query(5, description="Number of recommendations to return") ): """Get news recommendations based on article ID""" try: recommendations = recommender.recommend_by_article_id(article_id, top_k) return { "success": True, "article_id": article_id, "recommendations": recommendations, "count": len(recommendations) } except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting recommendations: {str(e)}") @app.post("/recommend-by-query") async def recommend_by_query(query_data: NewsQuery): """Get news recommendations based on text query""" try: recommendations = recommender.recommend_by_query(query_data.query, query_data.top_k) return { "success": True, "query": query_data.query, "recommendations": recommendations, "count": len(recommendations) } except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting recommendations: {str(e)}") @app.post("/recommend-by-interests") async def recommend_by_interests(interests_data: InterestsQuery): """Get news recommendations based on user interests""" try: recommendations = recommender.recommend_by_interests(interests_data.interests, interests_data.top_k) return { "success": True, "interests": interests_data.interests, "recommendations": recommendations, "count": len(recommendations) } except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting recommendations: {str(e)}") @app.get("/trending") async def get_trending_news(top_k: int = Query(10, description="Number of trending articles to return")): """Get trending news articles""" try: trending = recommender.get_trending_articles(top_k) return { "success": True, "trending_articles": trending, "count": len(trending) } except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting trending news: {str(e)}") @app.get("/articles") async def get_all_articles( source: Optional[str] = Query(None, description="Filter by news source"), limit: int = Query(50, description="Maximum number of articles to return") ): """Get all articles with optional filtering""" try: if source: articles = recommender.get_articles_by_source(source, limit) else: all_articles = recommender.vector_store.get_all_articles() articles = sorted(all_articles, key=lambda x: x.get('published_date', ''), reverse=True)[:limit] return { "success": True, "articles": articles, "count": len(articles), "source_filter": source } except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting articles: {str(e)}") @app.post("/search") async def search_articles(search_data: SearchQuery): """Advanced search with filters""" try: filters = {} if search_data.source: filters['source'] = search_data.source results = recommender.search_articles(search_data.query, filters, search_data.top_k) return { "success": True, "query": search_data.query, "filters": filters, "results": results, "count": len(results) } except Exception as e: raise HTTPException(status_code=500, detail=f"Error searching articles: {str(e)}") @app.get("/stats") async def get_stats(): """Get system statistics""" try: stats = recommender.get_store_stats() # Add RSS feed information stats['rss_feeds'] = settings.rss_feeds stats['embedding_model'] = settings.embedding_model return { "success": True, "statistics": stats } except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting stats: {str(e)}") # Run the application if __name__ == "__main__": uvicorn.run( "main:app", host=settings.host, port=settings.port, reload=settings.debug )