fix: handle concurrent idempotency key conflicts

This commit is contained in:
ik
2026-09-11 07:46:50 +07:00
parent d28ad31aca
commit fc963101b3
2 changed files with 12 additions and 2 deletions
+10
View File
@@ -18,6 +18,7 @@ from fastapi import Depends, FastAPI, File, Header, HTTPException, Query, Reques
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sqlalchemy import delete, func, select, text
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, joinedload
from .activity import activity_rows
@@ -514,7 +515,16 @@ def create_catch_report(
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)))
try:
db.commit()
except IntegrityError:
# Another request won the same idempotency key race.
db.rollback()
winner = db.scalar(select(SubmissionAttempt).where(SubmissionAttempt.idempotency_key == key_hash))
if winner and winner.catch_report:
replay_token = hmac.new(settings.rate_limit_secret.encode(), (key_hash + ":upload").encode(), hashlib.sha256).hexdigest()
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]})
return CatchReportAccepted(id=report.id, moderation_status=report.moderation_status.value, screenshot_upload_token=upload_token, idempotent=False)
+1 -1
View File
@@ -136,7 +136,7 @@ class SubmissionAttempt(Base):
__tablename__ = "submission_attempt"
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
client_hash: Mapped[str] = mapped_column(String(64), index=True)
idempotency_key: Mapped[str | None] = mapped_column(String(128), index=True)
idempotency_key: Mapped[str | None] = mapped_column(String(128), unique=True, index=True)
catch_report_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("catch_report.id"), nullable=True)
payload_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)