fix: statements submittedAt 멱등성 보장, 에러 로깅, moderation JSON 파싱 에러 처리 - #56
Conversation
…러 처리 - statements: fail-open/approved 경로 모두 submittedAt 설정 → 재제출 방지 멱등성 보장 - statements: StatementData.submittedAt 타입 string | null로 수정 - statements: 외부 catch 블록에 console.error 에러 로깅 추가 - moderation: JSON.parse를 try-catch로 감싸 파싱 실패 시 명확한 에러 전파 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough진술 저장 API에서 Changes진술 저장 및 모더레이션 오류 처리 개선
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/api/disputes/[id]/statements/route.ts (1)
176-189:⚠️ Potential issue | 🟠 Major | ⚡ Quick winfail-open 응답에도 저장된
submittedAt를 반환하세요.Line 176 및 Line 178에서
submittedAt를 저장하지만 Line 189는 여전히null을 반환합니다. Gemini 실패 경로에서 클라이언트가 방금 제출된 진술을 미제출 상태로 해석할 수 있습니다.수정 예시
- submittedAt: null, + submittedAt: statement.submittedAt?.toISOString() ?? null,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/disputes/`[id]/statements/route.ts around lines 176 - 189, The code saves submittedAt with a new Date() value in the create/update operation (lines 176 and 178), but the API response in the NextResponse.json return statement is hardcoding submittedAt to null (line 189). Replace the null value with the actual submittedAt timestamp from the statement object to ensure the response accurately reflects the data that was just saved to the database, preventing the client from misinterpreting the submission status.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/api/disputes/`[id]/statements/route.ts:
- Line 178: The update operations at lines 178 and 237 are not atomic and allow
concurrent requests to bypass the duplicate submission check at line 150. To fix
this, replace the unconditional `update` operation with a conditional
`updateMany` that only succeeds when `submittedAt: null`, ensuring idempotency.
When the updateMany returns zero updated records (indicating the statement was
already submitted), catch this scenario and return a 409 Conflict status code.
Alternatively, implement a unique constraint on the relevant identifier field
and handle the unique constraint violation error to return 409, preventing
duplicate moderation logs and ensuring atomic processing of the submission.
---
Outside diff comments:
In `@src/app/api/disputes/`[id]/statements/route.ts:
- Around line 176-189: The code saves submittedAt with a new Date() value in the
create/update operation (lines 176 and 178), but the API response in the
NextResponse.json return statement is hardcoding submittedAt to null (line 189).
Replace the null value with the actual submittedAt timestamp from the statement
object to ensure the response accurately reflects the data that was just saved
to the database, preventing the client from misinterpreting the submission
status.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: f69269e8-5bef-4b34-932a-cfcb9802023b
📒 Files selected for processing (2)
src/app/api/disputes/[id]/statements/route.tssrc/lib/ai/moderation.ts
upsert가 submittedAt 여부와 무관하게 update를 실행해 동시 요청이 모두 통과하는 경쟁 조건을 제거한다. - isNew: create 사용, P2002 unique 충돌 → StatementConflictError → 409 - !isNew: updateMany(where: submittedAt: null) 사용, count === 0 → StatementConflictError → 409 - fail-open/approved 경로 모두 동일 패턴 적용 - outer catch에서 StatementConflictError 분기하여 409 반환 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/api/disputes/[id]/statements/route.ts (1)
143-163:⚠️ Potential issue | 🔴 Critical삭제된 dispute로의 진술 제출 차단 필요
코딩 가이드라인 "Deleted rooms/disputes must be blocked from access"에 따라 현재 코드는 dispute의 삭제 상태를 검증해야 합니다.
disputeParticipant.findFirst()를 호출하기 전에 dispute를 조회하여deletedAt: null을 확인하세요. 다른 모든 dispute API 엔드포인트(disputes/[id]/route.ts,disputes/[id]/judge/route.ts,disputes/[id]/result/route.ts등)가 동일한 패턴으로 검증하고 있습니다.권장 수정
const dispute = await prisma.dispute.findFirst({ where: { id: disputeId, deletedAt: null } }) if (!dispute) { return NextResponse.json<ApiResponse>( { success: false, error: { code: 'DISPUTE_NOT_FOUND', message: '사건을 찾을 수 없습니다.' } }, { status: 404 } ) }이후
participant조회를 수행하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/disputes/`[id]/statements/route.ts around lines 143 - 163, Add a validation check for deleted disputes before querying for the participant. Before the `prisma.disputeParticipant.findFirst()` call in the route handler, first query the dispute using `prisma.dispute.findFirst()` with a condition to ensure `deletedAt: null`, checking that the dispute exists and is not deleted. If the dispute is not found, return a 404 response with error code DISPUTE_NOT_FOUND. Only proceed with the existing participant query and subsequent logic after confirming the dispute exists and has not been deleted.Source: Coding guidelines
🧹 Nitpick comments (1)
src/app/api/disputes/[id]/statements/route.ts (1)
203-209: 💤 Low valuefail-open 경로와 approved 경로 간 statement 재조회 방식 불일치
approved 경로(line 285)에서는
updateMany후findUniqueOrThrow로 실제 저장된 값을 조회하지만, fail-open 경로에서는 수동으로 객체를 재구성합니다.submittedAt의 경우new Date()를 별도로 호출하므로 DB에 저장된 타임스탬프와 응답 값이 미세하게 다를 수 있습니다.일관성을 위해 approved 경로처럼
findUnique로 조회하거나, 최소한updateMany호출 전에 timestamp를 변수로 캡처하여 재사용하는 것이 좋습니다.♻️ 타임스탬프 일관성 개선 예시
} else { + const now = new Date() const { count } = await prisma.disputeStatement.updateMany({ where: { id: existingStatement.id, submittedAt: null }, - data: { content, moderationStatus: 'pending', submittedAt: new Date() }, + data: { content, moderationStatus: 'pending', submittedAt: now }, }) if (count === 0) throw new StatementConflictError() statement = { ...existingStatement, content, moderationStatus: 'pending', - submittedAt: new Date(), - updatedAt: new Date(), + submittedAt: now, + updatedAt: now, } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/disputes/`[id]/statements/route.ts around lines 203 - 209, The fail-open path reconstructs the statement object manually with new Date() calls for submittedAt and updatedAt fields, which creates a timestamp mismatch with the actual database-stored values. To fix this, either query the statement from the database using findUnique after the updateMany call (consistent with the approved path approach at line 285), or capture the timestamp in a variable before the updateMany operation and reuse it in the manually reconstructed statement object instead of calling new Date() separately for each field assignment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/app/api/disputes/`[id]/statements/route.ts:
- Around line 143-163: Add a validation check for deleted disputes before
querying for the participant. Before the `prisma.disputeParticipant.findFirst()`
call in the route handler, first query the dispute using
`prisma.dispute.findFirst()` with a condition to ensure `deletedAt: null`,
checking that the dispute exists and is not deleted. If the dispute is not
found, return a 404 response with error code DISPUTE_NOT_FOUND. Only proceed
with the existing participant query and subsequent logic after confirming the
dispute exists and has not been deleted.
---
Nitpick comments:
In `@src/app/api/disputes/`[id]/statements/route.ts:
- Around line 203-209: The fail-open path reconstructs the statement object
manually with new Date() calls for submittedAt and updatedAt fields, which
creates a timestamp mismatch with the actual database-stored values. To fix
this, either query the statement from the database using findUnique after the
updateMany call (consistent with the approved path approach at line 285), or
capture the timestamp in a variable before the updateMany operation and reuse it
in the manually reconstructed statement object instead of calling new Date()
separately for each field assignment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 3d168fd1-8256-4967-b22a-f7ad91aa3fed
📒 Files selected for processing (1)
src/app/api/disputes/[id]/statements/route.ts
Summary
statements/route.ts:submittedAt미설정으로 인한 재제출 방지 로직 무력화 수정statements/route.ts: 외부 catch 블록 에러 로깅 추가moderation.ts:JSON.parse파싱 실패 시 try-catch로 명확한 에러 전파변경 상세
submittedAt 멱등성
submittedAt: new Date()설정null이라 line 146의 중복 제출 방지 체크(existingStatement?.submittedAt)가 동작하지 않았음StatementData인터페이스submittedAt: null → string | null수정에러 로깅
console.error('[disputes/statements] api error', { message })패턴 적용JSON 파싱
JSON.parse(jsonMatch[0])실패 시 catch로 감싸'Gemini moderation returned unparseable JSON'에러 throwTest plan
submittedAtDB 컬럼에 값 설정 확인🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes