language-learning-app/api/app/routers/bff/articles.py

101 lines
2.7 KiB
Python
Raw Normal View History

2026-03-27 11:04:05 +00:00
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
2026-03-27 11:04:05 +00:00
from sqlalchemy.ext.asyncio import AsyncSession
from app.domain.services.article_service import ArticleService
from app.outbound.postgres.repositories.article_repository import (
PostgresArticleOwnershipRepository,
PostgresArticleRepository,
)
2026-03-27 11:04:05 +00:00
from ...auth import verify_token
from ...outbound.postgres.database import get_db
from ...outbound.storage_client import get_storage_client
2026-03-27 11:04:05 +00:00
router = APIRouter(prefix="/articles", tags=["bff", "articles"])
def _make_article_service(db) -> ArticleService:
return ArticleService(
article_repository=PostgresArticleRepository(db),
article_ownership_repository=PostgresArticleOwnershipRepository(db),
)
2026-03-27 11:04:05 +00:00
class ArticleItem(BaseModel):
id: str
published_at: datetime | None
language: str
title: str
complexity: str
2026-03-27 11:04:05 +00:00
class ArticleListResponse(BaseModel):
articles: list[ArticleItem]
2026-03-27 11:04:05 +00:00
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
2026-03-27 11:04:05 +00:00
def _audio_url(key: str | None) -> str | None:
if key is None:
return None
return get_storage_client().get_url(key)
2026-03-27 11:04:05 +00:00
@router.get("", response_model=ArticleListResponse, status_code=200)
async def list_articles(
db: AsyncSession = Depends(get_db),
token_data: dict = Depends(verify_token),
2026-03-27 11:04:05 +00:00
) -> ArticleListResponse:
service = _make_article_service(db)
user_id = token_data["sub"]
articles = await service.get_articles_for_user(user_id=user_id)
2026-03-27 11:04:05 +00:00
return ArticleListResponse(
articles=[
ArticleItem(
id=a.id,
published_at=a.published_at,
language=a.language,
title=a.title,
complexity=a.target_complexity,
2026-03-27 11:04:05 +00:00
)
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),
2026-03-27 11:04:05 +00:00
) -> ArticleDetail:
uid: str = token_data["sub"]
service = _make_article_service(db)
article = await service.get_article_as_user(article_id, uid)
2026-03-27 11:04:05 +00:00
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),
2026-03-27 11:04:05 +00:00
)