Automated Action 2bc28761f5 Create file upload and download API
- Set up FastAPI project structure
- Implement database models and migrations for file metadata
- Create file upload endpoint with size validation
- Implement file download and listing functionality
- Add health check and API information endpoints
- Create comprehensive documentation
2025-06-09 13:33:57 +00:00

161 lines
4.5 KiB
Python

from fastapi import APIRouter, Depends, File as UploadedFile, 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: UploadedFile = UploadedFile(...),
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