from __future__ import annotations import uuid from fastapi import ( APIRouter, Depends, File as FastAPIFile, Form, HTTPException, Query, Response, UploadFile, status, ) from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession from cpv3.infrastructure.auth import get_current_user from cpv3.infrastructure.deps import get_storage from cpv3.infrastructure.storage.base import StorageService from cpv3.infrastructure.storage.utils import get_user_folder from cpv3.infrastructure.settings import get_settings from cpv3.db.session import get_db from cpv3.modules.files.schemas import ( FileCreate, FileInfoResponse, FileRead, FileUpdate, ) from cpv3.modules.files.service import FileService from cpv3.modules.users.models import User router = APIRouter(prefix="/api/files", tags=["Files"]) MAX_MB_SIZE = 1024 @router.post( "/upload/", response_model=FileInfoResponse, status_code=status.HTTP_201_CREATED ) async def upload_file( file: UploadFile = FastAPIFile(...), folder: str = Form(default=""), current_user: User = Depends(get_current_user), storage: StorageService = Depends(get_storage), ) -> FileInfoResponse: # Validate max file size (matches old behavior). file.file.seek(0, 2) size_bytes = file.file.tell() file.file.seek(0) max_size = MAX_MB_SIZE * 1024 * 1024 if size_bytes > max_size: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"File size exceeds the maximum limit of {MAX_MB_SIZE} MB.", ) user_folder = get_user_folder(current_user) resolved_folder = f"{user_folder}/{folder}" if folder else f"{user_folder}/user_upload" key = await storage.upload_fileobj( fileobj=file.file, file_name=file.filename or "upload.bin", folder=resolved_folder, gen_name=True, content_type=file.content_type, ) info = await storage.get_file_info(key) return FileInfoResponse( file_path=info.file_path, file_url=info.file_url, file_size=info.file_size, filename=file.filename, ) @router.get("/get_file/", response_model=FileInfoResponse) async def get_file_info( file_path: str = Query(...), current_user: User = Depends(get_current_user), storage: StorageService = Depends(get_storage), ) -> FileInfoResponse: if not current_user.is_staff: user_prefix = f"{get_user_folder(current_user)}/" if not file_path.startswith(user_prefix): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") if not await storage.exists(file_path): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found") info = await storage.get_file_info(file_path) return FileInfoResponse( file_path=info.file_path, file_url=info.file_url, file_size=info.file_size, filename=info.filename, ) @router.get("/local/{file_path:path}") async def get_local_file( file_path: str, current_user: User = Depends(get_current_user), ) -> FileResponse: _ = current_user settings = get_settings() full_path = (settings.local_storage_dir / file_path).resolve() if not full_path.exists(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found") return FileResponse(full_path) @router.get("/files/", response_model=list[FileRead]) async def list_file_entries( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> list[FileRead]: service = FileService(db) files = await service.list_files(requester=current_user) return [FileRead.model_validate(f) for f in files] @router.post("/files/", response_model=FileRead, status_code=status.HTTP_201_CREATED) async def create_file_entry( body: FileCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> FileRead: service = FileService(db) file = await service.create_file(requester=current_user, data=body) return FileRead.model_validate(file) @router.get("/files/{file_id}/", response_model=FileRead) async def retrieve_file_entry( file_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> FileRead: service = FileService(db) file = await service.get_file(file_id) if file is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found") if not current_user.is_staff and file.owner_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") return FileRead.model_validate(file) @router.patch("/files/{file_id}/", response_model=FileRead) async def patch_file_entry( file_id: uuid.UUID, body: FileUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> FileRead: service = FileService(db) file = await service.get_file(file_id) if file is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found") if not current_user.is_staff and file.owner_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") file = await service.update_file(file, body) return FileRead.model_validate(file) @router.delete("/files/{file_id}/", status_code=status.HTTP_204_NO_CONTENT) async def delete_file_entry( file_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Response: service = FileService(db) file = await service.get_file(file_id) if file is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found") if not current_user.is_staff and file.owner_id != current_user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") await service.delete_file(file) return Response(status_code=status.HTTP_204_NO_CONTENT)