"""Asynchronous conversion. This is the path for large documents.""" from __future__ import annotations import logging from fastapi import APIRouter, Response, status from fastapi.responses import FileResponse from ..deps import get_container from ..errors import ConversionError, JobNotFoundError from ..models import ConvertRequest, JobAccepted, JobState logger = logging.getLogger(__name__) router = APIRouter(prefix="/jobs", tags=["joby"]) def _result_url(job_id: str) -> str: root = get_container().settings.root_path.rstrip("/") return f"{root}/jobs/{job_id}/result" @router.post( "", response_model=JobAccepted, status_code=status.HTTP_202_ACCEPTED, summary="Zaradi konverzi do fronty", ) async def create_job(request: ConvertRequest) -> JobAccepted: job = get_container().jobs.submit(request) return JobAccepted( job_id=job.id, status=job.state.status, created_at=job.state.created_at, result_url=_result_url(job.id), ) @router.get("/{job_id}", response_model=JobState, summary="Stav jobu") async def job_state(job_id: str) -> JobState: job = get_container().jobs.get(job_id) state = job.state.model_copy() if state.status == "done": state.result_url = _result_url(job_id) return state @router.get( "/{job_id}/result", summary="Stahne hotove PDF", response_class=Response, responses={ 200: {"content": {"application/pdf": {}}, "description": "Hotove PDF."}, 404: {"description": "Job neexistuje nebo uz expiroval."}, 409: {"description": "Job jeste nedobehl nebo skoncil chybou."}, }, ) async def job_result(job_id: str): container = get_container() job = container.jobs.get(job_id) if job.state.status != "done": error = ConversionError( f"Vysledek neni k dispozici, job je ve stavu {job.state.status}.", {"status": job.state.status}, ) error.error_code = "result_not_ready" error.status_code = 409 raise error path = container.storage.result_path(job_id) if not path.exists(): raise JobNotFoundError("Soubor s vysledkem uz neexistuje, job pravdepodobne expiroval.") filename = job.request.filename or "dokument.pdf" return FileResponse( path, media_type="application/pdf", filename=filename, headers={ "X-Page-Count": str(job.state.page_count or 0), "X-Engine-Used": job.state.engine_used or "", }, ) @router.delete("/{job_id}", response_model=JobState, summary="Zrusi job nebo smaze jeho vysledek") async def delete_job(job_id: str) -> JobState: return get_container().jobs.cancel(job_id).state