"""Files router: upload, list, download, and delete files for vehicles.""" import os import uuid from fastapi import APIRouter, Depends, File as FastAPIFile, HTTPException, UploadFile, status from fastapi.responses import FileResponse as FastAPIFileResponse from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.dependencies import get_current_user, get_pagination from app.models.user import User from app.models.vehicle import Vehicle from app.schemas.file import ( FileDeleteResponse, FileListResponse, FileResponse, FileUploadResponse, ) from app.services import file_service, vehicle_service router = APIRouter(prefix="/vehicles", tags=["files"]) async def _verify_vehicle_exists( db: AsyncSession, vehicle_id: uuid.UUID ) -> Vehicle: """Verify that a vehicle exists, raising 404 if not.""" vehicle = await vehicle_service.get_vehicle_by_id(db, vehicle_id) if vehicle is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": { "code": "VEHICLE_NOT_FOUND", "message": "Vehicle not found", } }, ) return vehicle @router.get( "/{vehicle_id}/files", response_model=FileListResponse, status_code=status.HTTP_200_OK, ) async def list_files( vehicle_id: uuid.UUID, db: AsyncSession = Depends(get_db), pagination: dict = Depends(get_pagination), current_user: User = Depends(get_current_user), ): """List all files for a vehicle with pagination.""" await _verify_vehicle_exists(db, vehicle_id) files, total = await file_service.list_files( db, vehicle_id=vehicle_id, page=pagination["page"], page_size=pagination["page_size"], ) return FileListResponse( items=[FileResponse.model_validate(f) for f in files], total=total, page=pagination["page"], page_size=pagination["page_size"], ) @router.post( "/{vehicle_id}/files", response_model=FileUploadResponse, status_code=status.HTTP_201_CREATED, ) async def upload_file( vehicle_id: uuid.UUID, file: UploadFile = FastAPIFile(...), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Upload a file for a vehicle (multipart/form-data). Accepts images (jpg, png, webp), documents (pdf, doc, docx). Max file size: 20MB. """ await _verify_vehicle_exists(db, vehicle_id) # Read file content file_content = await file.read() file_size = len(file_content) # Check file size (20MB limit) if not file_service.validate_file_size(file_size, max_size_mb=20): raise HTTPException( status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail={ "error": { "code": "FILE_TOO_LARGE", "message": f"File size {file_size} bytes exceeds 20MB limit", } }, ) # Validate MIME type mime_type = file.content_type or "" if not file_service.validate_mime_type(mime_type, file.filename or ""): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={ "error": { "code": "INVALID_MIME_TYPE", "message": f"Unsupported file type: {mime_type}. Allowed: jpg, png, webp, pdf, doc, docx", } }, ) try: file_record = await file_service.upload_file( db, vehicle_id=vehicle_id, file_content=file_content, original_filename=file.filename or "unnamed", mime_type=mime_type, ) except ValueError as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={ "error": { "code": "UPLOAD_FAILED", "message": str(exc), } }, ) return FileUploadResponse.model_validate(file_record) @router.get( "/{vehicle_id}/files/{file_id}", status_code=status.HTTP_200_OK, ) async def download_file( vehicle_id: uuid.UUID, file_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Download a file by its ID.""" await _verify_vehicle_exists(db, vehicle_id) file_record = await file_service.get_file(db, vehicle_id, file_id) if file_record is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": { "code": "FILE_NOT_FOUND", "message": "File not found", } }, ) if not os.path.exists(file_record.file_path): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": { "code": "FILE_NOT_FOUND", "message": "File not found on disk", } }, ) return FastAPIFileResponse( path=file_record.file_path, filename=file_record.original_filename, media_type=file_record.mime_type, ) @router.delete( "/{vehicle_id}/files/{file_id}", response_model=FileDeleteResponse, status_code=status.HTTP_200_OK, ) async def delete_file( vehicle_id: uuid.UUID, file_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Delete a file by its ID.""" await _verify_vehicle_exists(db, vehicle_id) file_record = await file_service.delete_file(db, vehicle_id, file_id) if file_record is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": { "code": "FILE_NOT_FOUND", "message": "File not found", } }, ) return FileDeleteResponse(message="File deleted", id=file_record.id)