from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks from sqlalchemy.orm import Session from app.api import deps from app.models.user import User from app.models.news import NewsArticle, NewsSource, NewsCategory, SavedArticle from app.schemas.news import ( NewsSource as NewsSourceSchema, NewsCategory as NewsCategorySchema, SavedArticle as SavedArticleSchema, SavedArticleCreate, NewsResponse, ) from app.services.news import ( get_news_articles, fetch_and_store_news, get_saved_articles_for_user, save_article_for_user, delete_saved_article, ) from app.services.user import get_user_preference router = APIRouter() @router.get("/", response_model=NewsResponse) async def get_news( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 25, keywords: Optional[str] = None, sources: Optional[str] = None, categories: Optional[str] = None, countries: Optional[str] = None, languages: Optional[str] = None, refresh: bool = False, background_tasks: BackgroundTasks = None, ): """ Retrieve news articles with optional filtering. If refresh is True, the API will fetch fresh news from Mediastack. Otherwise, it will return news articles from the database. """ # Parse filters source_ids = None if sources: source_names = [s.strip() for s in sources.split(",")] source_query = db.query(NewsSource).filter(NewsSource.name.in_(source_names)) source_ids = [source.id for source in source_query.all()] category_ids = None if categories: category_names = [c.strip() for c in categories.split(",")] category_query = db.query(NewsCategory).filter(NewsCategory.name.in_(category_names)) category_ids = [category.id for category in category_query.all()] country_list = None if countries: country_list = [c.strip() for c in countries.split(",")] language_list = None if languages: language_list = [lang.strip() for lang in languages.split(",")] # If refresh is requested, fetch news from the API if refresh and background_tasks: background_tasks.add_task( fetch_and_store_news, db, keywords=keywords, sources=sources, categories=categories, countries=countries, languages=languages, ) # Get news from the database articles, total = get_news_articles( db, skip=skip, limit=limit, keywords=keywords, source_ids=source_ids, category_ids=category_ids, countries=country_list, languages=language_list, ) return {"items": articles, "total": total} @router.get("/personalized", response_model=NewsResponse) async def get_personalized_news( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 25, refresh: bool = False, background_tasks: BackgroundTasks = None, current_user: User = Depends(deps.get_current_user), ): """ Retrieve news articles based on the user's preferences. """ # Get user preferences preferences = get_user_preference(db, current_user.id) if not preferences: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User preferences not found", ) # If refresh is requested, fetch news from the API if refresh and background_tasks: background_tasks.add_task( fetch_and_store_news, db, keywords=preferences.keywords, sources=preferences.sources, categories=preferences.categories, countries=preferences.countries, languages=preferences.languages, ) # Parse filters from preferences source_ids = None if preferences.sources: source_names = [s.strip() for s in preferences.sources.split(",")] source_query = db.query(NewsSource).filter(NewsSource.name.in_(source_names)) source_ids = [source.id for source in source_query.all()] category_ids = None if preferences.categories: category_names = [c.strip() for c in preferences.categories.split(",")] category_query = db.query(NewsCategory).filter(NewsCategory.name.in_(category_names)) category_ids = [category.id for category in category_query.all()] country_list = None if preferences.countries: country_list = [c.strip() for c in preferences.countries.split(",")] language_list = None if preferences.languages: language_list = [lang.strip() for lang in preferences.languages.split(",")] # Get news from the database articles, total = get_news_articles( db, skip=skip, limit=limit, keywords=preferences.keywords, source_ids=source_ids, category_ids=category_ids, countries=country_list, languages=language_list, ) return {"items": articles, "total": total} @router.get("/refresh", response_model=NewsResponse) async def refresh_news( db: Session = Depends(deps.get_db), keywords: Optional[str] = None, sources: Optional[str] = None, categories: Optional[str] = None, countries: Optional[str] = None, languages: Optional[str] = None, limit: int = 100, ): """ Fetch fresh news from Mediastack API and return them. This endpoint will fetch news from the API and store them in the database. """ articles = await fetch_and_store_news( db, keywords=keywords, sources=sources, categories=categories, countries=countries, languages=languages, limit=limit, ) return {"items": articles, "total": len(articles)} @router.get("/sources", response_model=List[NewsSourceSchema]) async def get_sources( db: Session = Depends(deps.get_db), ): """ Get a list of news sources. """ sources = db.query(NewsSource).all() return sources @router.get("/categories", response_model=List[NewsCategorySchema]) async def get_categories( db: Session = Depends(deps.get_db), ): """ Get a list of news categories. """ categories = db.query(NewsCategory).all() return categories @router.get("/saved", response_model=List[SavedArticleSchema]) async def get_saved_articles( db: Session = Depends(deps.get_db), skip: int = 0, limit: int = 100, current_user: User = Depends(deps.get_current_user), ): """ Get articles saved by the current user. """ saved_articles, total = get_saved_articles_for_user( db, user_id=current_user.id, skip=skip, limit=limit ) return saved_articles @router.post("/saved", response_model=SavedArticleSchema) async def save_article( *, db: Session = Depends(deps.get_db), article_in: SavedArticleCreate, current_user: User = Depends(deps.get_current_user), ): """ Save an article for the current user. """ # Check if article exists article = db.query(NewsArticle).filter(NewsArticle.id == article_in.article_id).first() if not article: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Article not found", ) # Check if already saved existing = ( db.query(SavedArticle) .filter( SavedArticle.user_id == current_user.id, SavedArticle.article_id == article_in.article_id, ) .first() ) if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Article already saved", ) saved_article = save_article_for_user( db, user_id=current_user.id, article_id=article_in.article_id, notes=article_in.notes ) return saved_article @router.delete("/saved/{saved_article_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) async def remove_saved_article( saved_article_id: int, db: Session = Depends(deps.get_db), current_user: User = Depends(deps.get_current_user), ): """ Remove a saved article for the current user. """ success = delete_saved_article(db, user_id=current_user.id, saved_article_id=saved_article_id) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Saved article not found", ) return None