Multimodel_Rag / app /api /files.py
Dhrumil Parikh
deploy GeminiRAG
cdc55f4
Raw
History Blame Contribute Delete
3.59 kB
"""
File upload endpoint.
POST /v1/files/upload
- Validates file extension against EXTENSION_MAP (returns 400 for unsupported).
- Enforces 500 MB hard limit (returns 413).
- Sanitises the filename (strips directory components to prevent path traversal).
- Saves the file to UPLOAD_DIR/<job_uuid>/<filename>.
- Creates a Job row in PostgreSQL (status=PENDING, step='queued').
- Enqueues process_file.delay(job_id) on the Celery queue.
- Returns 202 Accepted with {job_id, filename, file_type, status}.
"""
import uuid
from datetime import datetime
from pathlib import Path
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from pydantic import BaseModel
from sqlmodel import Session
from app.config import settings
from app.deps import get_db, get_current_user
from app.models.db import Job, JobStatus, User
from app.observability.logging import get_logger
from app.workers.tasks import process_file, update_job_state
router = APIRouter()
log = get_logger()
EXTENSION_MAP = {
".pdf": "pdf",
".docx": "docx",
".xlsx": "xlsx",
".csv": "csv",
".png": "image",
".jpg": "image",
".jpeg": "image",
".webp": "image",
# video
".mp4": "video",
".mov": "video",
".avi": "video",
".mkv": "video",
".m4v": "video",
".webm": "video",
# audio
".mp3": "audio",
".wav": "audio",
".m4a": "audio",
".aac": "audio",
".flac": "audio",
".ogg": "audio",
}
MAX_FILE_SIZE_BYTES = 500 * 1024 * 1024 # 500 MB
class UploadResponse(BaseModel):
job_id: str
filename: str
file_type: str
status: str
@router.post("/files/upload", response_model=UploadResponse, status_code=202)
async def upload_file(
file: UploadFile = File(...),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
suffix = Path(file.filename).suffix.lower()
file_type = EXTENSION_MAP.get(suffix)
if not file_type:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Unsupported file type '{suffix}'. Allowed: {', '.join(EXTENSION_MAP.keys())}",
)
content = await file.read()
file_size = len(content)
if file_size > MAX_FILE_SIZE_BYTES:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File exceeds 500 MB limit",
)
# Sanitise filename — prevent path traversal
safe_filename = Path(file.filename).name
job_id = uuid.uuid4()
dest_dir = Path(settings.UPLOAD_DIR) / str(job_id)
dest_dir.mkdir(parents=True, exist_ok=True)
file_path = str(dest_dir / safe_filename)
with open(file_path, "wb") as f:
f.write(content)
job = Job(
id=job_id,
user_id=current_user.id,
filename=safe_filename,
file_type=file_type,
file_path=file_path,
file_size_bytes=file_size,
status=JobStatus.pending,
step="queued",
created_at=datetime.utcnow(),
updated_at=datetime.utcnow(),
)
db.add(job)
db.commit()
db.refresh(job)
update_job_state(db, job_id, JobStatus.pending, step="queued")
process_file.delay(str(job_id))
log.info(
"file_uploaded",
user_id=str(current_user.id),
filename=safe_filename,
file_type=file_type,
job_id=str(job_id),
file_size_bytes=file_size,
)
return UploadResponse(
job_id=str(job_id),
filename=safe_filename,
file_type=file_type,
status="PENDING",
)