Conversation
📝 WalkthroughWalkthrough클럽 가입 신청에 상태 필드( Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Service as ClubApplicationService
participant Repo as ClubApplyRepository
participant Model as ClubApply
participant DB as Database
rect rgba(100,150,200,0.5)
Note over Client,DB: 신청 승인 흐름
Client->>Service: approveClubApplication(clubId, userId)
Service->>Repo: findFirstByClubIdAndUserIdAndStatus(... PENDING ...)
Repo-->>Service: ClubApply (PENDING)
Service->>Model: approve()
Model->>Model: status = APPROVED
Service->>Repo: save(ClubApply)
Repo->>DB: UPDATE club_apply SET status='APPROVED'
DB-->>Repo: OK
end
sequenceDiagram
participant Service as ClubApplicationService
participant RepoQ as ClubApplyQuestionRepository
participant ModelQ as ClubApplyQuestion
participant DB as Database
rect rgba(150,200,100,0.5)
Note over Service,DB: 질문 교체(soft-delete) 흐름
Service->>RepoQ: findAllByClubIdOrderByDisplayOrderAsc(clubId)
RepoQ->>DB: SELECT ... WHERE deleted_at IS NULL ORDER BY display_order,id
DB-->>RepoQ: List<ClubApplyQuestion>
Service->>ModelQ: model.softDelete(now) (for removed ones)
ModelQ->>RepoQ: saveAll(updatedQuestions)
RepoQ->>DB: UPDATE club_apply_question SET deleted_at=?
DB-->>RepoQ: OK
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 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/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java (1)
151-184:⚠️ Potential issue | 🔴 Critical[LEVEL: high] 승인/거절 상태 전이가 동시 요청에서 원자적으로 보장되지 않습니다.
문제는approveClubApplication/rejectClubApplication이PENDING조회 후 엔티티 상태를 직접 변경해 경쟁 트랜잭션에서 마지막 커밋이 상태를 덮어쓸 수 있다는 점입니다. 같은 신청건에 승인과 거절이 거의 동시에 들어오면 멤버 추가·승인 이벤트가 실행된 뒤 최종status가REJECTED로 저장되는 불일치가 재현될 수 있습니다.WHERE id = :id AND status = PENDING조건부 전이(영향 행 수 1건 검증) 또는@Version기반 낙관적 락으로 상태 전이를 단일 성공으로 제한해 주세요; As per coding guidelines, "보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java` around lines 151 - 184, approveClubApplication/rejectClubApplication mutate ClubApply state after reading it, allowing race conditions; change to perform a conditional state transition (e.g., UPDATE ClubApply SET status = :APPROVED ... WHERE id = :id AND status = :PENDING) via clubApplyRepository (expose a repository method like approveIfPending(id) / rejectIfPending(id) that returns affected row count) and verify the update affected exactly 1 row (throw a concurrency-specific exception otherwise), or alternatively add `@Version` to ClubApply and rely on optimistic locking by saving and handling OptimisticLockException; ensure these changes are used in the methods (replace direct clubApply.approve()/reject() + save semantics with the conditional update or versioned save) while preserving the surrounding `@Transactional` boundary and event/publication only after a successful single-row transition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java`:
- Around line 207-219: The method isLatestApprovedApplicationByUser uses
latestApply.id.max() to determine "latest" which can diverge from chronological
order; update the subquery in isLatestApprovedApplicationByUser to pick the row
with the greatest createdAt (and use id as a tiebreaker) instead of max(id) —
e.g., change the subquery to select the apply with max(createdAt) (or
alternatively select one row using ORDER BY createdAt DESC, id DESC and limit 1)
while keeping the same filters (latestApply.club.id.eq(clubId),
latestApply.status.eq(ClubApplyStatus.APPROVED),
latestApply.user.id.eq(clubApply.user.id)).
---
Outside diff comments:
In
`@src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java`:
- Around line 151-184: approveClubApplication/rejectClubApplication mutate
ClubApply state after reading it, allowing race conditions; change to perform a
conditional state transition (e.g., UPDATE ClubApply SET status = :APPROVED ...
WHERE id = :id AND status = :PENDING) via clubApplyRepository (expose a
repository method like approveIfPending(id) / rejectIfPending(id) that returns
affected row count) and verify the update affected exactly 1 row (throw a
concurrency-specific exception otherwise), or alternatively add `@Version` to
ClubApply and rely on optimistic locking by saving and handling
OptimisticLockException; ensure these changes are used in the methods (replace
direct clubApply.approve()/reject() + save semantics with the conditional update
or versioned save) while preserving the surrounding `@Transactional` boundary and
event/publication only after a successful single-row transition.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
src/main/java/gg/agit/konect/domain/club/enums/ClubApplyStatus.javasrc/main/java/gg/agit/konect/domain/club/model/ClubApply.javasrc/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.javasrc/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.javasrc/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (3)
src/main/resources/db/migration/**/*.sql
⚙️ CodeRabbit configuration file
src/main/resources/db/migration/**/*.sql: Flyway 마이그레이션 리뷰 규칙:
- 버전 파일명 규칙(V{number}__{description}.sql) 위반 여부를 우선 확인한다.
- 이미 배포된 마이그레이션 수정/재번호 부여 위험이 있으면 반드시 차단 코멘트를 남긴다.
- 파괴적 변경(drop, rename 등)은 롤백 가능성과 운영 영향 관점에서 검토한다.
Files:
src/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sqlsrc/main/java/gg/agit/konect/domain/club/enums/ClubApplyStatus.javasrc/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.javasrc/main/java/gg/agit/konect/domain/club/model/ClubApply.javasrc/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.java
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/club/enums/ClubApplyStatus.javasrc/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.javasrc/main/java/gg/agit/konect/domain/club/model/ClubApply.javasrc/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.java
🧬 Code graph analysis (1)
src/main/java/gg/agit/konect/domain/club/model/ClubApply.java (2)
src/main/java/gg/agit/konect/domain/club/model/ClubRecruitment.java (1)
Entity(28-146)src/main/java/gg/agit/konect/domain/university/model/University.java (1)
Entity(20-53)
src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
동아리 가입 신청(ClubApply)을 삭제 기반에서 상태 전이(PENDING/APPROVED/REJECTED) 기반으로 전환하고, 가입 문항(ClubApplyQuestion)에 소프트 딜리트를 도입해 “지원 시점 기준”으로 문항/답변 정합성을 유지하도록 개선하는 PR입니다.
Changes:
- ClubApply에
status를 추가하고 승인/거절 시 삭제 대신 상태 전이로 처리 - ClubApplyQuestion에
deleted_at(soft delete) 도입 및 문항 교체 시 soft-delete + 신규 생성 방식으로 이력 보존 - 답변 조회 시 “현재 문항”이 아닌 “지원 시점에 유효한 문항”을 조회하도록 쿼리 변경
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql | club_apply에 status/state(유니크 보조) 추가, club_apply_question에 deleted_at 추가 |
| src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java | 승인/거절을 상태 전이로 변경, 답변 조회 시점 기반 문항 조회로 변경, 문항 교체 로직을 soft-delete + 신규 생성으로 변경 |
| src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java | PENDING 중심 조회/존재 확인으로 변경, 최신 승인 지원서 조회 추가 |
| src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java | soft-delete 반영 조회 및 “지원 시점 유효 문항” 조회 쿼리 추가 |
| src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.java | 지원서 목록/승인 멤버 목록 조회에서 status 조건(PENDING/APPROVED) 반영 |
| src/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.java | deletedAt 및 softDelete/isSame 유틸 추가 |
| src/main/java/gg/agit/konect/domain/club/model/ClubApply.java | status 필드/전이 메서드(approve/reject) 추가, 유니크 제약(state)으로 변경 |
| src/main/java/gg/agit/konect/domain/club/enums/ClubApplyStatus.java | 지원 상태 enum 신설 |
src/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java
Outdated
Show resolved
Hide resolved
src/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java
Show resolved
Hide resolved
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 (2)
src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java (2)
151-183:⚠️ Potential issue | 🔴 Critical[LEVEL: high] 승인/거절 상태 전이에 동시성 제어가 없습니다.
문제: Line [151]~Line [183]에서 동일 신청 건을 조회 후 처리할 때 락/버전 검증이 없어 두 관리자가 동시에 approve/reject를 실행하면 둘 다 진행될 수 있습니다.
영향: 한 트랜잭션은 멤버 생성을 완료하고 다른 트랜잭션은 거절 알림을 발송하는 식으로 상태와 부수효과가 불일치하는 운영 장애가 발생합니다(동일applicationId에 동시 요청으로 재현 가능).
제안: 승인/거절 조회를PESSIMISTIC_WRITE로 고정하거나WHERE status = PENDING조건부 갱신 후 영향 행 수를 검증해 단일 전이만 허용해 주세요; As per coding guidelines, '보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java` around lines 151 - 183, Concurrent transitions on the same ClubApply (seen where clubApplyRepository.getByIdAndClubId, clubApply.approve(), and clubApply.reject() are called in the approve and reject flows) are not protected, so add concurrency control: either load the application with a PESSIMISTIC_WRITE lock (replace getByIdAndClubId with a locking query or use EntityManager.lock) or perform a conditional update that includes "WHERE status = PENDING" and verify the affected row count to ensure only one transition succeeds; after the conditional update, throw a conflict/duplicate-transition exception if no row was updated. Ensure the fix is applied in both the approve and reject flows and that side effects (ClubMember save, chatRoomMembershipService.addClubMember, and event publish) only run after the successful guarded state transition.
201-213:⚠️ Potential issue | 🟠 Major[LEVEL: medium] 중복 신청 경쟁 조건에서 500 에러가 노출될 수 있습니다.
문제: Line [201]~Line [213]은existsPendingByClubIdAndUserId확인 후save를 수행하는 TOCTOU 구조라 동일 사용자/동아리로 동시 신청 시 둘 다 검증을 통과할 수 있습니다.
영향: DB 유니크 제약 충돌이 커밋 시점DataIntegrityViolationException으로 터지고 비즈니스 예외로 변환되지 않으면 간헐적 500 응답이 발생합니다(더블클릭/재시도 트래픽에서 재현 가능).
제안: 저장 시점 예외를ALREADY_APPLIED_CLUB로 매핑할 수 있도록 flush 지점에서 처리하거나, 사용자-동아리 키 단위 락으로 신청 생성 구간을 직렬화해 주세요; As per coding guidelines, '보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java` around lines 201 - 213, The existsPendingByClubIdAndUserId pre-check can race and cause a DB unique-constraint failure on clubApplyRepository.save (TOCTOU); wrap the save/flush in a try/catch that catches DataIntegrityViolationException and rethrow CustomException.of(ALREADY_APPLIED_CLUB) so DB uniqueness errors map to the business error (e.g., try { ClubApply apply = clubApplyRepository.save(ClubApply.of(...)); clubApplyRepository.flush(); } catch (DataIntegrityViolationException e) { throw CustomException.of(ALREADY_APPLIED_CLUB); }). Alternatively, serialize creation by acquiring a lock on the user-club key before calling existsPendingByClubIdAndUserId/save if you prefer pessimistic locking. Ensure the change references existsPendingByClubIdAndUserId, clubApplyRepository.save, clubApplyRepository.flush, ClubApply.of and ALREADY_APPLIED_CLUB.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java`:
- Around line 27-29: The JPQL/SQL filter in ClubApplyQuestionRepository excludes
questions deleted exactly at the application time because it uses
"question.deletedAt > :appliedAt"; change that predicate to "question.deletedAt
>= :appliedAt" (i.e., include equality) in the query string where the query
orders by question.displayOrder and question.id, and add a unit/integration test
that creates a question and an application with identical timestamps to assert
the question is still returned/mapped; update any repository method name
referencing this query accordingly so the behaviour is covered.
---
Outside diff comments:
In
`@src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java`:
- Around line 151-183: Concurrent transitions on the same ClubApply (seen where
clubApplyRepository.getByIdAndClubId, clubApply.approve(), and
clubApply.reject() are called in the approve and reject flows) are not
protected, so add concurrency control: either load the application with a
PESSIMISTIC_WRITE lock (replace getByIdAndClubId with a locking query or use
EntityManager.lock) or perform a conditional update that includes "WHERE status
= PENDING" and verify the affected row count to ensure only one transition
succeeds; after the conditional update, throw a conflict/duplicate-transition
exception if no row was updated. Ensure the fix is applied in both the approve
and reject flows and that side effects (ClubMember save,
chatRoomMembershipService.addClubMember, and event publish) only run after the
successful guarded state transition.
- Around line 201-213: The existsPendingByClubIdAndUserId pre-check can race and
cause a DB unique-constraint failure on clubApplyRepository.save (TOCTOU); wrap
the save/flush in a try/catch that catches DataIntegrityViolationException and
rethrow CustomException.of(ALREADY_APPLIED_CLUB) so DB uniqueness errors map to
the business error (e.g., try { ClubApply apply =
clubApplyRepository.save(ClubApply.of(...)); clubApplyRepository.flush(); }
catch (DataIntegrityViolationException e) { throw
CustomException.of(ALREADY_APPLIED_CLUB); }). Alternatively, serialize creation
by acquiring a lock on the user-club key before calling
existsPendingByClubIdAndUserId/save if you prefer pessimistic locking. Ensure
the change references existsPendingByClubIdAndUserId, clubApplyRepository.save,
clubApplyRepository.flush, ClubApply.of and ALREADY_APPLIED_CLUB.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (10)
src/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.javasrc/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubSettingsService.javasrc/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (3)
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.javasrc/main/java/gg/agit/konect/domain/club/service/ClubSettingsService.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.javasrc/main/java/gg/agit/konect/domain/club/service/ClubService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/java/gg/agit/konect/domain/club/model/ClubApplyQuestion.javasrc/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sqlsrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.javasrc/main/java/gg/agit/konect/domain/club/service/ClubSettingsService.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyQueryRepository.javasrc/main/java/gg/agit/konect/domain/club/service/ClubService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.javasrc/main/java/gg/agit/konect/domain/club/service/ClubRecruitmentService.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyAnswerRepository.javasrc/main/java/gg/agit/konect/domain/club/repository/ClubApplyRepository.java
src/main/resources/db/migration/**/*.sql
⚙️ CodeRabbit configuration file
src/main/resources/db/migration/**/*.sql: Flyway 마이그레이션 리뷰 규칙:
- 버전 파일명 규칙(V{number}__{description}.sql) 위반 여부를 우선 확인한다.
- 이미 배포된 마이그레이션 수정/재번호 부여 위험이 있으면 반드시 차단 코멘트를 남긴다.
- 파괴적 변경(drop, rename 등)은 롤백 가능성과 운영 영향 관점에서 검토한다.
Files:
src/main/resources/db/migration/V42__add_soft_delete_to_club_apply_and_question.sql
src/main/java/gg/agit/konect/domain/club/repository/ClubApplyQuestionRepository.java
Show resolved
Hide resolved
| @Table( | ||
| name = "club_apply", | ||
| uniqueConstraints = @UniqueConstraint( | ||
| name = "uq_club_apply_club_id_user_id", | ||
| columnNames = {"club_id", "user_id"} | ||
| name = "uq_club_apply_club_id_user_id_state", | ||
| columnNames = {"club_id", "user_id", "state"} | ||
| ) | ||
| ) |
There was a problem hiding this comment.
club_apply 테이블의 유니크 제약이 state 컬럼을 참조하도록 바뀌었는데, 엔티티에는 state 필드 매핑이 없습니다. 현재 테스트 프로필에서 spring.jpa.hibernate.ddl-auto=create-drop을 사용하고 있어, Hibernate가 스키마를 생성할 때 state 컬럼 없이 유니크 제약을 만들려다 DDL 오류가 발생할 가능성이 큽니다. state를 읽기 전용 컬럼(예: insertable=false, updatable=false)으로 엔티티에 매핑하거나, 테스트 환경에서는 Flyway로 스키마를 생성하도록 바꾸는 방식으로 해결해 주세요.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/gg/agit/konect/domain/club/model/ClubApply.java`:
- Around line 81-87: The approve() and reject() methods on ClubApply currently
change this.status unconditionally; update them to validate the current state
(use ClubApplyStatus, e.g., ensure status == ClubApplyStatus.PENDING) before
transitioning to APPROVED or REJECTED and throw an appropriate exception (e.g.,
IllegalStateException or a domain-specific exception) when the transition is
invalid; alternatively, if you intend to enforce this in the service layer, add
a comment documenting that responsibility and ensure service methods perform the
same check for approve/reject calls.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
src/main/java/gg/agit/konect/domain/club/model/ClubApply.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/club/model/ClubApply.java
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/java/gg/agit/konect/domain/club/model/ClubApply.java
| public void approve() { | ||
| this.status = ClubApplyStatus.APPROVED; | ||
| } | ||
|
|
||
| public void reject() { | ||
| this.status = ClubApplyStatus.REJECTED; | ||
| } |
There was a problem hiding this comment.
[LEVEL: medium] approve()/reject() 메서드에서 현재 상태 검증 누락
문제: 현재 상태가 PENDING인지 확인하지 않고 상태를 변경합니다.
영향: 이미 REJECTED된 지원서를 APPROVED로 변경하거나, APPROVED를 REJECTED로 변경하는 잘못된 상태 전이가 발생할 수 있습니다.
제안: 상태 전이 전 현재 상태 검증 로직을 추가하거나, 서비스 레이어에서 검증이 보장되는지 확인하세요.
제안 코드
public void approve() {
+ if (this.status != ClubApplyStatus.PENDING) {
+ throw new IllegalStateException("PENDING 상태에서만 승인할 수 있습니다.");
+ }
this.status = ClubApplyStatus.APPROVED;
}
public void reject() {
+ if (this.status != ClubApplyStatus.PENDING) {
+ throw new IllegalStateException("PENDING 상태에서만 거절할 수 있습니다.");
+ }
this.status = ClubApplyStatus.REJECTED;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public void approve() { | |
| this.status = ClubApplyStatus.APPROVED; | |
| } | |
| public void reject() { | |
| this.status = ClubApplyStatus.REJECTED; | |
| } | |
| public void approve() { | |
| if (this.status != ClubApplyStatus.PENDING) { | |
| throw new IllegalStateException("PENDING 상태에서만 승인할 수 있습니다."); | |
| } | |
| this.status = ClubApplyStatus.APPROVED; | |
| } | |
| public void reject() { | |
| if (this.status != ClubApplyStatus.PENDING) { | |
| throw new IllegalStateException("PENDING 상태에서만 거절할 수 있습니다."); | |
| } | |
| this.status = ClubApplyStatus.REJECTED; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/gg/agit/konect/domain/club/model/ClubApply.java` around lines
81 - 87, The approve() and reject() methods on ClubApply currently change
this.status unconditionally; update them to validate the current state (use
ClubApplyStatus, e.g., ensure status == ClubApplyStatus.PENDING) before
transitioning to APPROVED or REJECTED and throw an appropriate exception (e.g.,
IllegalStateException or a domain-specific exception) when the transition is
invalid; alternatively, if you intend to enforce this in the service layer, add
a comment documenting that responsibility and ensure service methods perform the
same check for approve/reject calls.
🔍 개요
기존 구조에서는 승인/거절 이후 재지원 시 유니크 충돌 위험이 있었고,
관리자가 문항을 변경/삭제하면 과거 지원 답변 조회에서 질문 매핑 정합성이 깨질 여지가 있었습니다.
이를 해결하기 위해 상태 전이 + 시점 기반 문항 조회로 모델을 명확화했습니다.
🚀 주요 변경 내용
club_apply테이블의status,state컬럼을 추가했습니다.status는 동아리 지원의 진행 과정인PENDING/APPROVED/REJECTED을 나타냅니다.(club_id, user_id, status)를 유니크로 걸면 승인과 거절에 대한 데이터가 1개만 존재해야 하는 문제가 있습니다.(club_id, user_id, state)를 유니크로 걸고state는PENDING상태에서는 1, 그 이외의 상태라면null로 지정하여 이 문제를 해결했습니다.가입 문항(ClubApplyQuestion)에 소프트 딜리트(deleted_at)를 도입해 문항 이력을 보존하도록 변경했습니다.
승인 멤버 답변 조회를 “현재 문항”이 아니라 “지원 시점에 유효한 문항” 기준으로 조회하도록 변경했습니다.
문항 PUT 교체 시 하드 삭제/인플레이스 수정 대신 soft-delete + 신규 생성으로 이력 정합성을 유지합니다.
💬 참고 사항
✅ Checklist (완료 조건)