
- Fix code linting issues - Update README with detailed documentation - Configure database paths for the current environment - Create necessary directory structure The News Aggregation Service is now ready to use with FastAPI and SQLite.
285 lines
8.5 KiB
Python
285 lines
8.5 KiB
Python
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,
|
|
current_user: Optional[User] = Depends(deps.get_current_active_user),
|
|
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,
|
|
current_user: User = Depends(deps.get_current_active_user),
|
|
refresh: bool = False,
|
|
background_tasks: BackgroundTasks = None,
|
|
):
|
|
"""
|
|
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,
|
|
current_user: User = Depends(deps.get_current_active_user),
|
|
):
|
|
"""
|
|
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_active_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_active_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_active_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 |