from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.domain.services.article_service import ArticleService from app.outbound.postgres.repositories.article_repository import ( PostgresArticleOwnershipRepository, PostgresArticleRepository, ) from ...auth import verify_token from ...outbound.postgres.database import get_db from ...outbound.storage_client import get_storage_client router = APIRouter(prefix="/articles", tags=["bff", "articles"]) def _make_article_service(db) -> ArticleService: return ArticleService( article_repository=PostgresArticleRepository(db), article_ownership_repository=PostgresArticleOwnershipRepository(db), ) class ArticleItem(BaseModel): id: str published_at: datetime | None language: str title: str complexity: str class ArticleListResponse(BaseModel): articles: list[ArticleItem] class ArticleDetail(BaseModel): id: str published_at: datetime | None language: str complexity: str title: str body: str audio_url: str | None body_pos: dict | None def _audio_url(key: str | None) -> str | None: if key is None: return None return get_storage_client().get_url(key) @router.get("", response_model=ArticleListResponse, status_code=200) async def list_articles( db: AsyncSession = Depends(get_db), token_data: dict = Depends(verify_token), ) -> ArticleListResponse: service = _make_article_service(db) user_id = token_data["sub"] articles = await service.get_articles_for_user(user_id=user_id) return ArticleListResponse( articles=[ ArticleItem( id=a.id, published_at=a.published_at, language=a.language, title=a.title, complexity=a.target_complexity, ) for a in articles ] ) @router.get("/{article_id}", response_model=ArticleDetail, status_code=200) async def get_article( article_id: str, db: AsyncSession = Depends(get_db), token_data: dict = Depends(verify_token), ) -> ArticleDetail: uid: str = token_data["sub"] service = _make_article_service(db) article = await service.get_article_as_user(article_id, uid) if article is None: raise HTTPException(status_code=404, detail="Article not found") return ArticleDetail( id=article.id, published_at=article.published_at, language=article.language, complexity=article.target_complexity, title=article.title, body=article.text, body_pos=article.text_linguistic_data, audio_url=_audio_url(article.audio_key), )