From 0b7bbc78a6c353e59e2487f0c1555d6fbe9b010f Mon Sep 17 00:00:00 2001 From: IK Date: Fri, 11 Sep 2026 07:47:36 +0700 Subject: [PATCH] fix: make report and idempotency insert transactional --- apps/api/app/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/api/app/main.py b/apps/api/app/main.py index a75941d..7f6a76d 100644 --- a/apps/api/app/main.py +++ b/apps/api/app/main.py @@ -510,11 +510,11 @@ def create_catch_report( if idempotency_key else secrets.token_urlsafe(32)) report = CatchReport(fish=fish, spot=spot, waterbody=waterbody, bait=bait, weight_g=payload.weight_g, fishing_method=payload.fishing_method, rig_type=payload.rig_type, retrieve_method=payload.retrieve_method, retrieve_speed=payload.retrieve_speed, caught_at=payload.caught_at, reported_at=datetime.now(timezone.utc), player_name=payload.player_name, source_type=SourceType.user, source_url=payload.source_url, source_confidence=60, moderation_status=ModerationStatus.pending, raw_payload={"comment": payload.comment} if payload.comment else None, screenshot_upload_token_hash=hashlib.sha256(upload_token.encode()).hexdigest()) db.add(report) - db.commit() # Store idempotency key if provided if idempotency_key: key_hash = hmac.new(settings.rate_limit_secret.encode(), idempotency_key.encode(), hashlib.sha256).hexdigest() - db.add(SubmissionAttempt(client_hash="", idempotency_key=key_hash, catch_report_id=report.id, payload_hash=payload_hash, created_at=datetime.now(timezone.utc))) + # One transaction: a unique-key race must roll back the report too. + db.add(SubmissionAttempt(client_hash="", idempotency_key=key_hash, catch_report=report, payload_hash=payload_hash, created_at=datetime.now(timezone.utc))) try: db.commit() except IntegrityError: @@ -526,6 +526,8 @@ def create_catch_report( return JSONResponse(status_code=200, content={"id": str(winner.catch_report.id), "moderation_status": winner.catch_report.moderation_status.value, "screenshot_upload_token": replay_token, "idempotent": True}) raise logger.info("idempotency key stored", extra={"idempotency_key": idempotency_key[:8]}) + else: + db.commit() return CatchReportAccepted(id=report.id, moderation_status=report.moderation_status.value, screenshot_upload_token=upload_token, idempotent=False)