fix: make screenshot retry idempotent
This commit is contained in:
@@ -19,7 +19,7 @@ from ..dependencies import Db
|
||||
from ..importer import normalize
|
||||
from ..models import Bait, BaitKind, CatchReport, Fish, ModerationStatus, SourceType, Spot, SubmissionAttempt, Waterbody
|
||||
from ..schemas import CatchReportAccepted, CatchReportCreate
|
||||
from ..storage import ScreenshotError, upload_screenshot
|
||||
from ..storage import ScreenshotError, delete_screenshot, upload_screenshot
|
||||
from ..submission_security import check_rate_limit
|
||||
from ..tackle_components import replace_tackle_components
|
||||
from rf4_research.gear_components import from_catch_fields
|
||||
@@ -77,21 +77,31 @@ def create_catch_report(payload: CatchReportCreate, request: Request, db: Db, id
|
||||
|
||||
@router.post("/api/v1/catch-reports/{report_id}/screenshot", status_code=204, response_class=Response)
|
||||
def add_screenshot(report_id: UUID, db: Db, screenshot: UploadFile = File(), upload_token: Annotated[str | None, Header(alias="X-Upload-Token")] = None) -> Response:
|
||||
report = db.get(CatchReport, report_id)
|
||||
report_query = select(CatchReport).where(CatchReport.id == report_id)
|
||||
if db.bind is not None and db.bind.dialect.name == "postgresql":
|
||||
report_query = report_query.with_for_update()
|
||||
report = db.scalar(report_query)
|
||||
if report is None or report.source_type != SourceType.user or report.moderation_status != ModerationStatus.pending:
|
||||
raise HTTPException(status_code=404, detail="pending catch report not found")
|
||||
supplied_hash = hashlib.sha256((upload_token or "").encode()).hexdigest()
|
||||
if not report.screenshot_upload_token_hash or not hmac.compare_digest(report.screenshot_upload_token_hash, supplied_hash):
|
||||
raise HTTPException(status_code=401, detail="invalid screenshot upload token")
|
||||
if report.screenshot_key:
|
||||
raise HTTPException(status_code=409, detail="screenshot already uploaded")
|
||||
return Response(status_code=204)
|
||||
raw = screenshot.file.read(settings.screenshot_max_bytes + 1)
|
||||
try:
|
||||
report.screenshot_key = upload_screenshot(raw, filename=screenshot.filename, content_type=screenshot.content_type)
|
||||
except ScreenshotError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
report.screenshot_upload_token_hash = None
|
||||
db.commit()
|
||||
try:
|
||||
db.commit()
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
try:
|
||||
delete_screenshot(report.screenshot_key)
|
||||
except Exception:
|
||||
logger.exception("failed to remove orphaned screenshot", extra={"screenshot_key": report.screenshot_key})
|
||||
raise HTTPException(status_code=503, detail="screenshot could not be saved; please retry") from exc
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user