- Add FastAPI app with CORS and auth middleware (Bearer token) - Add POST /api/v1/transactions/upload endpoint (multipart/form-data) - Add MinIO storage service with date-partitioned object keys - Add PostgreSQL database service for transaction metadata - Add Dockerfile and docker-compose.yml for deployment - Add config.example.yaml template Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
import logging
|
|
|
|
from fastapi import APIRouter, File, Form, UploadFile
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.services import database, minio
|
|
|
|
router = APIRouter(tags=["upload"])
|
|
logger = logging.getLogger(__name__)
|
|
|
|
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
|
|
|
|
|
|
@router.post("/transactions/upload")
|
|
async def upload_transaction(
|
|
file: UploadFile = File(...),
|
|
original_filename: str | None = Form(None),
|
|
user_note: str | None = Form(None),
|
|
):
|
|
# Read file content
|
|
content = await file.read()
|
|
|
|
if len(content) == 0:
|
|
return JSONResponse(
|
|
status_code=422,
|
|
content={"success": False, "error": "Missing required field: file"},
|
|
)
|
|
|
|
if len(content) > MAX_FILE_SIZE:
|
|
return JSONResponse(
|
|
status_code=413,
|
|
content={"success": False, "error": "File size exceeds 10MB limit"},
|
|
)
|
|
|
|
filename = original_filename or file.filename or "upload.jpg"
|
|
|
|
# Upload to MinIO
|
|
try:
|
|
object_key = minio.upload_image(content, filename)
|
|
except Exception:
|
|
logger.exception("Failed to upload image to storage")
|
|
return JSONResponse(
|
|
status_code=503,
|
|
content={"success": False, "error": "Storage service unavailable"},
|
|
)
|
|
|
|
# Insert metadata into PostgreSQL
|
|
try:
|
|
result = database.insert_transaction(
|
|
image_object_key=object_key,
|
|
original_filename=filename,
|
|
user_note=user_note if user_note else None,
|
|
)
|
|
except Exception:
|
|
logger.exception("Failed to save transaction metadata")
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"success": False, "error": "Failed to save transaction metadata"},
|
|
)
|
|
|
|
return {"success": True, "data": result}
|