from fastapi import APIRouter, Depends, UploadFile, HTTPException, status from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from typing import List from app.db.session import get_db from app.models.file import File from app.schemas.file import FileResponse from app.services.file_service import FileService router = APIRouter() @router.post("/", response_model=FileResponse, status_code=status.HTTP_201_CREATED) async def upload_file( file: UploadFile, db: Session = Depends(get_db), ): """ Upload a file to the server. The file will be stored in the server's file system, and its metadata will be saved in the database. """ try: # Save the file to disk unique_filename, file_path, file_size = await FileService.save_file(file) # Create a new file record in the database db_file = File( filename=unique_filename, original_filename=file.filename or "unnamed_file", content_type=file.content_type or "application/octet-stream", file_size=file_size, file_path=file_path, ) db.add(db_file) db.commit() db.refresh(db_file) # Construct the download URL download_url = f"/api/v1/files/{db_file.id}/download" # Return the file metadata with download URL return { **db_file.__dict__, "download_url": download_url, } except HTTPException as e: # Re-raise HTTP exceptions raise e except Exception as e: # Log the error and return a generic error message print(f"Error uploading file: {str(e)}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An error occurred while uploading the file", ) @router.get("/", response_model=List[FileResponse]) def list_files(db: Session = Depends(get_db)): """ List all files stored in the system. Returns a list of file metadata. """ files = db.query(File).all() # Add download URLs to each file for file in files: file.__dict__["download_url"] = f"/api/v1/files/{file.id}/download" return files @router.get("/{file_id}", response_model=FileResponse) def get_file(file_id: int, db: Session = Depends(get_db)): """ Get metadata for a specific file by ID. """ file = db.query(File).filter(File.id == file_id).first() if not file: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="File not found", ) # Add download URL to the file file.__dict__["download_url"] = f"/api/v1/files/{file.id}/download" return file @router.get("/{file_id}/download") def download_file(file_id: int, db: Session = Depends(get_db)): """ Download a file by its ID. Returns the file as a streaming response. """ # Get the file metadata from the database file = db.query(File).filter(File.id == file_id).first() if not file: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="File not found", ) # Check if the file exists on disk if not FileService.file_exists(file.filename): # If the file does not exist on disk, update the database db.delete(file) db.commit() raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="File not found on disk", ) # Get file content file_content = FileService.get_file_content(file.filename) # Return the file as a streaming response return StreamingResponse( content=file_content, media_type=file.content_type, headers={ "Content-Disposition": f'attachment; filename="{file.original_filename}"', }, ) @router.delete( "/{file_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None ) def delete_file(file_id: int, db: Session = Depends(get_db)): """ Delete a file by its ID. Removes the file from both the database and the file system. """ # Get the file metadata from the database file = db.query(File).filter(File.id == file_id).first() if not file: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="File not found", ) # Delete the file from disk FileService.delete_file(file.filename) # Delete the file metadata from the database db.delete(file) db.commit() return None