import logging import uuid from fastapi import APIRouter, File, Form, UploadFile from fastapi.responses import JSONResponse from app.models.transaction import ConfirmRequest, PresignRequest from app.services import database, minio router = APIRouter(tags=["upload"]) logger = logging.getLogger(__name__) MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB # In-memory pending references (ref_id → object_key) _pending_refs: dict[str, str] = {} @router.post("/transactions/presign") async def presign_upload(req: PresignRequest): object_key = minio._generate_object_key(req.original_filename) ref_id = uuid.uuid4().hex try: upload_url = minio.generate_presigned_put_url(object_key) except Exception: logger.exception("Failed to generate presigned URL") return JSONResponse( status_code=503, content={"success": False, "error": "Storage service unavailable"}, ) _pending_refs[ref_id] = object_key return { "success": True, "data": { "upload_url": upload_url, "object_key": object_key, "ref_id": ref_id, }, } @router.post("/transactions/confirm") async def confirm_upload(req: ConfirmRequest): expected_key = _pending_refs.pop(req.ref_id, None) if expected_key is None: return JSONResponse( status_code=404, content={"success": False, "error": "Invalid or expired reference ID"}, ) if req.object_key != expected_key: return JSONResponse( status_code=400, content={"success": False, "error": "Object key mismatch"}, ) try: result = database.insert_transaction( image_object_key=req.object_key, original_filename=req.original_filename, user_note=req.user_note if req.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} @router.post("/transactions/upload") async def upload_transaction( file: UploadFile = File(...), original_filename: str | None = Form(None), user_note: str | None = Form(None), ): # Legacy fallback endpoint — receives image via backend relay 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" 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"}, ) 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}