Skip to content

[Fix] 팀원 초대 코드 재사용 + 만료 기간 단축 수정#65

Merged
LeeJeongHeon02 merged 2 commits into
devfrom
feat/team-invite-code-reuse
Jul 21, 2026
Merged

[Fix] 팀원 초대 코드 재사용 + 만료 기간 단축 수정#65
LeeJeongHeon02 merged 2 commits into
devfrom
feat/team-invite-code-reuse

Conversation

@LeeJeongHeon02

@LeeJeongHeon02 LeeJeongHeon02 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • 재사용: POST /api/v1/teams/{teamId}/invite-code를 호출할 때, 기존 활성 코드가 아직
    만료 전이면 그대로 재사용해 반환한다. "팀원 초대하기" 버튼을 여러 번 눌러도 유효 기간
    안에는 같은 코드가 나온다. 없거나 이미 만료된 경우에만(있으면 먼저 폐기 후) 새로 발급한다.
  • 만료 기간 단축: 1년 → 1일. 예전엔 "누를 때마다 항상 새 코드"였고 재발급하지 않으면
    자동 갱신도 안 됐던 탓에, 방치된 팀이 초대 자체를 못 하게 되는 걸 막으려고 1년으로 길게
    잡았었다. 재사용 방식으로 바뀌면서 그 근거가 없어졌고, 오히려 유출된 코드가 오래 살아있는
    게 더 큰 위험이라 짧게 줄였다.

구현

  • TeamInviteCodeTransactionService.issueOnce(): 기존 코드를 조회해 isUsable(now)(사실상
    만료 여부만 확인 — maxUses는 초대 코드 발급 시 항상 null)면 그대로 반환. 아니면 있는
    경우에 한해 revoke() 후 새로 발급한다.
    • DB의 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
    (만료된 코드를 직접 심어두고 호출 시 새 코드로 교체되는지) 신규 추가
  • 전체 테스트 스위트 통과 (310개, 0 실패)
  • 로컬 서버로 실제 API를 3회 연속 호출해 매번 같은 코드/만료시각이 오고 DB에 행이
    1개만 남는지 확인, H2에서 expires_at을 강제로 과거로 돌린 뒤 다시 호출해 기존 코드는
    폐기되고 새 코드가 발급되는지 확인

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 새 기능

    • 유효 기간 내 초대 코드를 반복 요청하면 기존 코드를 재사용합니다.
    • 초대 코드는 발급 후 1일간 유효합니다.
    • 코드가 만료됐거나 없으면 기존 코드를 폐기한 뒤 새 코드를 발급합니다.
  • 문서

    • 초대 코드 발급 API와 팀원 초대 절차가 변경된 동작에 맞게 업데이트되었습니다.
    • API 설명과 만료 기간 안내가 최신 정책으로 정리되었습니다.

버튼을 누를 때마다 항상 새 코드를 발급하던 걸, 활성 코드가 아직 안 만료됐으면
그대로 재사용하도록 바꿨다. 없거나 만료된 경우에만(있으면 먼저 폐기 후) 새로
발급한다. 만료 기간도 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>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@LeeJeongHeon02, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5f471228-a6ba-4d41-bc3a-9c68585cb5cd

📥 Commits

Reviewing files that changed from the base of the PR and between 42c1c27 and b264860.

📒 Files selected for processing (3)
  • src/main/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeTransactionService.java
  • src/main/resources/db/migration/V13__revoke_all_active_invite_codes.sql
  • src/test/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeIntegrationTest.java
📝 Walkthrough

Walkthrough

초대 코드 발급이 유효한 기존 코드를 재사용하고, 코드가 없거나 만료된 경우에만 기존 코드를 폐기한 뒤 새 코드를 발급하도록 변경됐다. 새 코드의 유효 기간은 1일로 단축됐으며, 관련 문서와 통합 테스트가 갱신됐다.

Changes

초대 코드 생명주기

Layer / File(s) Summary
초대 코드 계약 및 API 문서
docs/mvp-database-erd.md, docs/team-creation-api-spec.md, src/main/java/com/shinhan/klljs/domain/team/entity/TeamInviteLink.java, src/main/java/com/shinhan/klljs/domain/team/controller/TeamMemberControllerDocs.java
팀당 미폐기 코드 제약, 유효 코드 재사용, 만료 후 폐기·재발급 순서와 1일 만료 정책이 문서 및 주석에 반영됐다.
초대 코드 발급 흐름
src/main/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeTransactionService.java
issueOnce가 사용 가능한 기존 코드를 반환하고, 만료된 경우에만 revoke 후 1일 TTL의 새 코드를 발급하도록 변경됐다.
발급 시나리오 통합 검증
src/test/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeIntegrationTest.java
주입된 Clock을 사용해 유효 코드 재사용과 만료 코드 폐기 후 새 코드 발급을 검증한다.

Estimated code review effort: 3 (보통) | ~20 minutes

Possibly related PRs

  • Shinhan-KLLJS/backend#8: TeamInviteLink의 활성 코드 제약과 revoke 순서 관련 변경이 겹친다.
  • Shinhan-KLLJS/backend#49: 초대 코드 서비스와 엔티티의 활성·폐기 처리 및 코드 정책을 함께 변경한다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 팀원 초대 코드 재사용과 만료 기간 단축이라는 핵심 변경을 간결하게 잘 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/team-invite-code-reuse

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.mdTeamMemberControllerDocs와 실제 통합 테스트(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의 요구사항에 따라, 여러 스레드에서 동시에 초대 코드를 발급할 때 하나만 정상 발급되거나 유효 코드가 안전하게 재사용되는지를 검증하는 동시성 경계 테스트(@RepeatedTestExecutorService 활용 등) 추가를 권장합니다.

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between ea3067b and 42c1c27.

📒 Files selected for processing (6)
  • docs/mvp-database-erd.md
  • docs/team-creation-api-spec.md
  • src/main/java/com/shinhan/klljs/domain/team/controller/TeamMemberControllerDocs.java
  • src/main/java/com/shinhan/klljs/domain/team/entity/TeamInviteLink.java
  • src/main/java/com/shinhan/klljs/domain/team/service/TeamInviteCodeTransactionService.java
  • src/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>
@LeeJeongHeon02

Copy link
Copy Markdown
Contributor Author

두 피드백 모두 반영했습니다 (커밋 b264860).

[P1] isReusable() 헬퍼로 분리해서 getInviteCode() != null 조건을 추가했습니다. V11 이전 해시 저장 방식에서 넘어온 레거시 행(inviteCode NULL, 아직 미만료·미폐기)을 재현하는 회귀 테스트(issue_reissuesWhenPreviousCodeIsLegacyNullInviteCode)도 추가했습니다.

[P2] V13__revoke_all_active_invite_codes.sql 마이그레이션으로 배포 시점에 폐기되지 않은 초대 코드를 전부 revoke하도록 했습니다(즉시 전체 적용 선택). 하드 삭제가 아니라 revoke만 하므로 team_members.joined_via_invite_id 참조 무결성은 그대로 유지됩니다. 실제 MySQL 8.0.46 컨테이너에 V1~V11을 적용하고 세 가지 케이스(1년짜리 활성 코드/레거시 NULL 코드/이미 폐기된 코드)를 심어둔 뒤 V13을 실행해, 앞의 두 개만 이번 마이그레이션 시각으로 revoke되고 이미 폐기된 건 그대로인 것까지 확인했습니다.

전체 테스트 311개 통과.

@LeeJeongHeon02
LeeJeongHeon02 merged commit 1d7a9eb into dev Jul 21, 2026
1 of 2 checks passed
@LeeJeongHeon02
LeeJeongHeon02 deleted the feat/team-invite-code-reuse branch July 21, 2026 15:55
@LeeJeongHeon02 LeeJeongHeon02 changed the title feat: 팀원 초대 코드 재사용 + 만료 기간 단축 [Fix] 팀원 초대 코드 재사용 + 만료 기간 단축 수정 Jul 21, 2026
@LeeJeongHeon02 LeeJeongHeon02 self-assigned this Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant