API: allow free access to audio files; create the article_service
This commit is contained in:
parent
6a08da1ff6
commit
407d423a4c
11 changed files with 132 additions and 18 deletions
0
api/app/domain/__init__.py
Normal file
0
api/app/domain/__init__.py
Normal file
15
api/app/domain/models/translated_article.py
Normal file
15
api/app/domain/models/translated_article.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class TranslatedArticle:
|
||||
id: str
|
||||
|
||||
source_lang: str
|
||||
source_title: str
|
||||
source_text: str
|
||||
|
||||
target_lang: str
|
||||
target_title: str
|
||||
target_text: str
|
||||
|
||||
|
||||
30
api/app/domain/services/article_service.py
Normal file
30
api/app/domain/services/article_service.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import re
|
||||
|
||||
from ..models.summarise_job import SummariseJob
|
||||
from ..models.translated_article import TranslatedArticle
|
||||
|
||||
def first_heading(md: str) -> str | None:
|
||||
m = re.search(r'^#{1,2}\s+(.+)', md, re.MULTILINE)
|
||||
return m.group(1).strip() if m else None
|
||||
|
||||
class ArticleService:
|
||||
def __init__(self, summarise_job_repository):
|
||||
self.summarise_job_repository = summarise_job_repository
|
||||
|
||||
async def get_all_articles(self) -> list[TranslatedArticle]:
|
||||
summarise_jobs = await self.summarise_job_repository.list_all()
|
||||
return summarise_jobs.map(self.summarise_job_to_translated_article)
|
||||
|
||||
def summarise_job_to_translated_article(
|
||||
self,
|
||||
summarise_job: SummariseJob,
|
||||
) -> TranslatedArticle:
|
||||
return TranslatedArticle(
|
||||
id=summarise_job.id,
|
||||
source_lang=summarise_job.target_language, # The source language for the article is the target language of the job
|
||||
source_title=first_heading(summarise_job.translated_text) or "",
|
||||
source_text=summarise_job.translated_text,
|
||||
target_lang=summarise_job.source_language, # The target language for the article is the source language of the job
|
||||
target_title=first_heading(summarise_job.generated_text) or "",
|
||||
target_text=summarise_job.generated_text,
|
||||
)
|
||||
|
|
@ -8,6 +8,7 @@ from .routers.api import jobs
|
|||
from .routers import auth as auth_router
|
||||
from .routers import media as media_router
|
||||
from .routers.api.main import api_router
|
||||
from .routers.bff.main import bff_router
|
||||
from .storage import ensure_bucket_exists
|
||||
from . import worker
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ async def lifespan(app: FastAPI):
|
|||
app = FastAPI(title="Language Learning API", lifespan=lifespan)
|
||||
|
||||
app.include_router(api_router)
|
||||
app.include_router(bff_router)
|
||||
app.include_router(auth_router.router)
|
||||
app.include_router(media_router.router)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ class Base(DeclarativeBase):
|
|||
pass
|
||||
|
||||
|
||||
async def get_db():
|
||||
async def get_db() -> AsyncSession:
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
|
|
|
|||
|
|
@ -5,7 +5,51 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..entities.summarise_job_entity import SummariseJobEntity
|
||||
from ....domain.models.summarise_job import SummariseJob
|
||||
|
||||
class PostgresSummariseJobRepository:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def list_all(self) -> list[SummariseJob]:
|
||||
result = self.db.execute(
|
||||
select(SummariseJobEntity).order_by(SummariseJobEntity.created_at.desc())
|
||||
)
|
||||
|
||||
return list(result.scalars().all()).map(self.entity_to_model)
|
||||
|
||||
async def get_by_audio_url(
|
||||
self,
|
||||
audio_url: str
|
||||
) -> SummariseJob | None:
|
||||
result = await self.db.execute(
|
||||
select(SummariseJobEntity).where(
|
||||
SummariseJobEntity.audio_url == audio_url
|
||||
)
|
||||
)
|
||||
|
||||
return self.entity_to_model(result.scalar_one_or_none())
|
||||
|
||||
def entity_to_model(self, entity: SummariseJobEntity | None) -> SummariseJob:
|
||||
if entity is None:
|
||||
return None
|
||||
|
||||
return SummariseJob(
|
||||
id=str(entity.id),
|
||||
user_id=str(entity.user_id),
|
||||
status=entity.status,
|
||||
source_language=entity.source_language,
|
||||
target_language=entity.target_language,
|
||||
complexity_level=entity.complexity_level,
|
||||
input_summary=entity.input_summary,
|
||||
generated_text=entity.generated_text,
|
||||
translated_text=entity.translated_text,
|
||||
error_message=entity.error_message,
|
||||
audio_url=entity.audio_url,
|
||||
created_at=entity.created_at,
|
||||
started_at=entity.started_at,
|
||||
completed_at=entity.completed_at,
|
||||
)
|
||||
|
||||
async def update(db: AsyncSession, job: SummariseJobEntity) -> None:
|
||||
await db.commit()
|
||||
|
|
@ -41,18 +85,6 @@ async def list_all(db: AsyncSession) -> list[SummariseJobEntity]:
|
|||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def get_by_audio_url_and_user(
|
||||
db: AsyncSession, audio_url: str, user_id: uuid.UUID
|
||||
) -> SummariseJobEntity | None:
|
||||
result = await db.execute(
|
||||
select(SummariseJobEntity).where(
|
||||
SummariseJobEntity.audio_url == audio_url,
|
||||
SummariseJobEntity.user_id == user_id,
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def mark_processing(db: AsyncSession, job: SummariseJobEntity) -> None:
|
||||
job.status = "processing"
|
||||
job.started_at = datetime.now(timezone.utc)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ class GenerationRequest(BaseModel):
|
|||
target_language: str
|
||||
complexity_level: str
|
||||
input_texts: list[str]
|
||||
topic: str | None = None
|
||||
source_language: str = "en"
|
||||
|
||||
|
||||
|
|
|
|||
0
api/app/routers/bff/__init__.py
Normal file
0
api/app/routers/bff/__init__.py
Normal file
30
api/app/routers/bff/articles.py
Normal file
30
api/app/routers/bff/articles.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ...domain.services.article_service import ArticleService
|
||||
from ...outbound.postgres.database import get_db, AsyncSessionLocal
|
||||
from ...outbound.postgres.repositories.summarise_job_repository import PostgresSummariseJobRepository
|
||||
|
||||
|
||||
router = APIRouter(prefix="/articles", tags=["articles"])
|
||||
|
||||
|
||||
class ArticleResponse(BaseModel):
|
||||
target_language: str
|
||||
complexity_level: str
|
||||
input_texts: list[str]
|
||||
|
||||
class ArticlesResponse(BaseModel):
|
||||
articles: list[ArticleResponse]
|
||||
|
||||
@router.get("", response_model=ArticlesResponse, status_code=200)
|
||||
async def get_articles(
|
||||
db = Depends(get_db),
|
||||
) -> ArticlesResponse:
|
||||
service = ArticleService(PostgresSummariseJobRepository(db))
|
||||
|
||||
try:
|
||||
articles = await service.get_all_articles()
|
||||
return ArticlesResponse(articles=articles)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
7
api/app/routers/bff/main.py
Normal file
7
api/app/routers/bff/main.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from .articles import router as article_router
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
bff_router = APIRouter(prefix="/bff", tags=["bff"])
|
||||
|
||||
bff_router.include_router(article_router)
|
||||
|
|
@ -7,7 +7,7 @@ from botocore.exceptions import ClientError
|
|||
|
||||
from ..auth import verify_token
|
||||
from ..outbound.postgres.database import get_db
|
||||
from ..outbound.postgres.repositories import summarise_job_repository
|
||||
from ..outbound.postgres.repositories.summarise_job_repository import PostgresSummariseJobRepository
|
||||
from ..storage import download_audio
|
||||
|
||||
router = APIRouter(prefix="/media", tags=["media"])
|
||||
|
|
@ -17,11 +17,10 @@ router = APIRouter(prefix="/media", tags=["media"])
|
|||
async def get_media_file(
|
||||
filename: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
token_data: dict = Depends(verify_token),
|
||||
) -> Response:
|
||||
user_id = uuid.UUID(token_data["sub"])
|
||||
repository = PostgresSummariseJobRepository(db)
|
||||
job = await repository.get_by_audio_url(filename)
|
||||
|
||||
job = await summarise_job_repository.get_by_audio_url_and_user(db, filename, user_id)
|
||||
if job is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue