from fastapi import APIRouter, Depends, Header, Request, status from fastapi.responses import Response from app.api.dependencies import get_upload_service from app.schemas.common import ErrorResponse from app.services.upload_service import CreateUploadInput, UploadService router = APIRouter() TUS_VERSION = "1.0.0" @router.post("", status_code=status.HTTP_201_CREATED, responses={409: {"model": ErrorResponse}}) async def create_tus_upload( upload_length: int = Header(alias="Upload-Length"), upload_metadata: str | None = Header(default=None, alias="Upload-Metadata"), tus_resumable: str = Header(alias="Tus-Resumable"), service: UploadService = Depends(get_upload_service), ) -> Response: if tus_resumable != TUS_VERSION: return Response(status_code=status.HTTP_412_PRECONDITION_FAILED) upload_session = await service.create_upload( CreateUploadInput(upload_length=upload_length, upload_metadata=upload_metadata) ) return Response( status_code=status.HTTP_201_CREATED, headers={ "Location": upload_session.tus_upload_url or f"/files/{upload_session.id}", "Tus-Resumable": TUS_VERSION, }, ) @router.head("/{upload_id}") async def head_tus_upload( upload_id: str, tus_resumable: str = Header(alias="Tus-Resumable"), service: UploadService = Depends(get_upload_service), ) -> Response: if tus_resumable != TUS_VERSION: return Response(status_code=status.HTTP_412_PRECONDITION_FAILED) upload_session = await service.get_upload(upload_id) return Response( status_code=status.HTTP_200_OK, headers={ "Tus-Resumable": TUS_VERSION, "Upload-Offset": str(upload_session.received_size_bytes), "Upload-Length": str(upload_session.total_size_bytes), }, ) @router.patch("/{upload_id}", status_code=status.HTTP_204_NO_CONTENT, responses={409: {"model": ErrorResponse}}) async def patch_tus_upload( upload_id: str, request: Request, upload_offset: int = Header(alias="Upload-Offset"), tus_resumable: str = Header(alias="Tus-Resumable"), content_type: str = Header(alias="Content-Type"), service: UploadService = Depends(get_upload_service), ) -> Response: if tus_resumable != TUS_VERSION: return Response(status_code=status.HTTP_412_PRECONDITION_FAILED) if content_type != "application/offset+octet-stream": return Response(status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) chunk = await request.body() upload_session = await service.append_upload_bytes(upload_id, upload_offset, chunk) return Response( status_code=status.HTTP_204_NO_CONTENT, headers={ "Tus-Resumable": TUS_VERSION, "Upload-Offset": str(upload_session.received_size_bytes), }, )