[Fix] 팀원 초대 코드 재사용 + 만료 기간 단축 수정#65
Conversation
버튼을 누를 때마다 항상 새 코드를 발급하던 걸, 활성 코드가 아직 안 만료됐으면 그대로 재사용하도록 바꿨다. 없거나 만료된 경우에만(있으면 먼저 폐기 후) 새로 발급한다. 만료 기간도 1년 -> 1일로 줄였다 - 재사용 방식으로 바뀌면서 "방치된 팀이 초대를 못 하게 되는 걸 막기 위해 길게 잡는다"는 예전 근거가 없어졌고, 유출된 코드가 오래 살아있는 위험이 더 커서다. - TeamInviteCodeTransactionService.issueOnce(): findByTeamIdAndRevokedAtIsNull로 찾은 기존 코드가 isUsable(now)면(= 아직 미만료, maxUses는 항상 null이라 사실상 만료 여부만 봄) 그대로 반환. 아니면 있는 경우에 한해 revoke 후 새로 발급 - activeCodeMarker 유니크 인덱스가 revoked_at IS NULL 행을 팀당 1개로만 제한하므로 (expires_at은 안 봄) 이 순서를 지켜야 한다. - 만료 기간 상수화(INVITE_CODE_TTL_DAYS = 1). 로컬 서버로 실제 API를 호출해 확인: 연속 3회 호출이 같은 코드/만료시각을 반환하고 DB에 행이 1개만 남는지(재사용), H2에서 만료 시각을 강제로 과거로 돌린 뒤 호출하면 기존 코드는 폐기되고 새 코드가 발급되는지(재발급) 둘 다 확인했다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 1 minute Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthrough초대 코드 발급이 유효한 기존 코드를 재사용하고, 코드가 없거나 만료된 경우에만 기존 코드를 폐기한 뒤 새 코드를 발급하도록 변경됐다. 새 코드의 유효 기간은 1일로 단축됐으며, 관련 문서와 통합 테스트가 갱신됐다. Changes초대 코드 생명주기
Estimated code review effort: 3 (보통) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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 (3)
docs/team-creation-api-spec.md (2)
529-533: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win초대 코드 저장 방식에 대한 설명이 실제 스키마 및 구현과 다릅니다.
이 문서에는 초대 코드가 해시되어
token_hash에 저장된다고 기재되어 있으나,mvp-database-erd.md와 실제 엔티티(TeamInviteLink.inviteCode) 구현은 암호화되지 않은 평문 저장을 명시하고 있습니다. 현재 구현 내용과 일치하도록 문서를 갱신해 주세요.🤖 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 `@docs/team-creation-api-spec.md` around lines 529 - 533, Update the “저장 방식” section to document that invite codes are stored as plaintext in TeamInviteLink.inviteCode, matching mvp-database-erd.md and the entity implementation. Remove the TokenHasher.sha256(), token_hash, one-time response, and unrecoverable-hash claims, and describe retrieval behavior consistently with the current implementation.
497-499: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win권한 정책 문서가 실제 코드 및 다른 문서와 불일치합니다.
이 문서에는
MEMBER역할이 403을 반환한다고 기재되어 있으나,mvp-database-erd.md및TeamMemberControllerDocs와 실제 통합 테스트(issue_allowsMemberRole)에서는MEMBER역할의 초대 코드 발급을 정상적으로 허용하고 있습니다. 실제 코드의 허용 정책에 맞게 문서를 수정해 주세요.🤖 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 `@docs/team-creation-api-spec.md` around lines 497 - 499, Update the team creation API permission statement in docs/team-creation-api-spec.md so MEMBER invitation-code issuance is documented as allowed, matching the implementation, TeamMemberControllerDocs, mvp-database-erd.md, and issue_allowsMemberRole integration test; remove the incorrect 403 restriction while preserving the OWNER/ADMIN permissions.src/test/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeIntegrationTest.java (1)
28-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win동시성 경계 테스트 추가를 권장합니다.
TeamInviteCodeTransactionService는 비관적 락(findByIdForUpdate)을 사용해 동시 발급 요청 시 유니크 인덱스 위반을 방지하고 있습니다. Path instruction의 요구사항에 따라, 여러 스레드에서 동시에 초대 코드를 발급할 때 하나만 정상 발급되거나 유효 코드가 안전하게 재사용되는지를 검증하는 동시성 경계 테스트(@RepeatedTest및ExecutorService활용 등) 추가를 권장합니다.🤖 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/test/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeIntegrationTest.java` around lines 28 - 30, TeamInviteCodeIntegrationTest에 TeamInviteCodeTransactionService의 동시 발급 경계를 검증하는 테스트를 추가하세요. `@RepeatedTest와` ExecutorService 등을 사용해 여러 스레드가 동시에 초대 코드 발급을 요청하도록 구성하고, 하나만 성공하거나 동일한 유효 코드가 안전하게 재사용되는 결과를 검증하세요.Source: Path instructions
🤖 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 `@docs/team-creation-api-spec.md`:
- Around line 529-533: Update the “저장 방식” section to document that invite codes
are stored as plaintext in TeamInviteLink.inviteCode, matching
mvp-database-erd.md and the entity implementation. Remove the
TokenHasher.sha256(), token_hash, one-time response, and unrecoverable-hash
claims, and describe retrieval behavior consistently with the current
implementation.
- Around line 497-499: Update the team creation API permission statement in
docs/team-creation-api-spec.md so MEMBER invitation-code issuance is documented
as allowed, matching the implementation, TeamMemberControllerDocs,
mvp-database-erd.md, and issue_allowsMemberRole integration test; remove the
incorrect 403 restriction while preserving the OWNER/ADMIN permissions.
In
`@src/test/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeIntegrationTest.java`:
- Around line 28-30: TeamInviteCodeIntegrationTest에
TeamInviteCodeTransactionService의 동시 발급 경계를 검증하는 테스트를 추가하세요. `@RepeatedTest와`
ExecutorService 등을 사용해 여러 스레드가 동시에 초대 코드 발급을 요청하도록 구성하고, 하나만 성공하거나 동일한 유효 코드가
안전하게 재사용되는 결과를 검증하세요.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 31661dc7-0f36-4564-88e3-fc40257bd271
📒 Files selected for processing (6)
docs/mvp-database-erd.mddocs/team-creation-api-spec.mdsrc/main/java/com/shinhan/klljs/domain/team/controller/TeamMemberControllerDocs.javasrc/main/java/com/shinhan/klljs/domain/team/entity/TeamInviteLink.javasrc/main/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeTransactionService.javasrc/test/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeIntegrationTest.java
[P1] findByTeamIdAndRevokedAtIsNull로 찾은 행이 V11 이전 해시 저장 방식에서 넘어와 inviteCode가 NULL인 레거시 행이면, isUsable()은 이를 걸러내지 못해(만료·폐기·사용횟수만 확인) inviteCode: null을 그대로 응답해버리는 문제가 있었다. 재사용 조건에 getInviteCode() != null을 추가(isReusable 헬퍼로 분리)하고 회귀 테스트를 추가했다. [P2] 이 PR 이전에 발급된 1년짜리 코드는 새 1일 정책과 무관하게 원래 만료 시각까지 계속 재사용됐다. V13 마이그레이션으로 배포 시점에 폐기되지 않은 초대 코드를 전부 revoke해서, 다음 발급 호출부터 모든 팀이 즉시 1일 정책을 적용받게 했다(하드 삭제가 아니라 revoke만 하므로 team_members.joined_via_invite_id로 참조되는 합류 이력은 그대로 보존된다). 같은 조건(revoked_at IS NULL)에 걸리는 레거시 NULL 코드 행도 함께 정리된다. 실제 MySQL 8.0.46 컨테이너에 V1~V11을 적용하고 세 가지 상태(1년짜리 활성 코드, 레거시 NULL 코드 활성 행, 이미 폐기된 행)를 심어둔 뒤 V13을 적용해 확인함: 앞의 두 행만 이번 마이그레이션 실행 시각으로 revoke되고, 이미 폐기돼 있던 행은 원래 폐기 시각 그대로 유지됨. active_code_marker도 세 행 모두 NULL로 정상 갱신됨. 전체 테스트 스위트 통과(311개, 0 실패). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
두 피드백 모두 반영했습니다 (커밋 b264860). [P1] [P2] 전체 테스트 311개 통과. |
Summary
POST /api/v1/teams/{teamId}/invite-code를 호출할 때, 기존 활성 코드가 아직만료 전이면 그대로 재사용해 반환한다. "팀원 초대하기" 버튼을 여러 번 눌러도 유효 기간
안에는 같은 코드가 나온다. 없거나 이미 만료된 경우에만(있으면 먼저 폐기 후) 새로 발급한다.
자동 갱신도 안 됐던 탓에, 방치된 팀이 초대 자체를 못 하게 되는 걸 막으려고 1년으로 길게
잡았었다. 재사용 방식으로 바뀌면서 그 근거가 없어졌고, 오히려 유출된 코드가 오래 살아있는
게 더 큰 위험이라 짧게 줄였다.
구현
TeamInviteCodeTransactionService.issueOnce(): 기존 코드를 조회해isUsable(now)(사실상만료 여부만 확인 —
maxUses는 초대 코드 발급 시 항상null)면 그대로 반환. 아니면 있는경우에 한해
revoke()후 새로 발급한다.active_code_marker유니크 인덱스(팀당revoked_at IS NULL인 행 1개 제한)는revoked_at만 보고expires_at은 안 보기 때문에, 만료된 코드가 있으면 반드시 먼저폐기한 뒤에 새로 insert해야 한다 — 순서를 지키지 않으면 유니크 인덱스 위반으로 즉시
실패한다.
INVITE_CODE_TTL_DAYS = 1상수로 뽑았다.Test plan
TeamInviteCodeIntegrationTest:issue_revokesPreviousInviteAndLeavesOneActiveCode(항상새 코드 발급을 검증하던 기존 테스트)를
issue_reusesActiveCodeWithinValidityWindow로교체(연속 호출 시 같은 코드/만료시각, 행 1개만 존재)하고,
issue_reissuesWhenPreviousCodeExpired(만료된 코드를 직접 심어두고 호출 시 새 코드로 교체되는지) 신규 추가
1개만 남는지 확인, H2에서
expires_at을 강제로 과거로 돌린 뒤 다시 호출해 기존 코드는폐기되고 새 코드가 발급되는지 확인
🤖 Generated with Claude Code
Summary by CodeRabbit
새 기능
문서