Conversation
Walkthrough그룹 참여 및 취소 기능이 V2 API에 추가되었습니다. 에러 코드 이름이 일관성 있게 변경되었고, 새로운 DTO와 서비스 메서드들이 구현되었으며, 저장소 쿼리 메서드가 확장되었습니다. 컨트롤러에 두 개의 새 엔드포인트( Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Controller as GroupV2Controller
participant Service as GroupV2Service
participant Repository as GroupUserV2Repository
participant DB as Database
rect rgb(200, 220, 240)
Note over Client,DB: 그룹 참여 흐름 (attend)
Client->>Controller: POST /api/v2/groups/{groupId}/attend
activate Controller
Controller->>Service: attend(userId, groupId)
activate Service
Service->>Service: userId null 검증
Service->>DB: findGroupById(groupId)
activate DB
DB-->>Service: GroupV2 entity
deactivate DB
Service->>Service: 그룹 존재 확인
Service->>Service: 호스트 제한 확인
Service->>Service: 모집 상태 확인
Service->>Repository: findByGroupIdAndUserId(groupId, userId)
activate Repository
Repository->>DB: query
activate DB
DB-->>Repository: Optional<GroupUserV2>
deactivate DB
deactivate Repository
alt 기존 멤버십 있음
Service->>Service: 상태 확인 (ATTEND 시 에러)
else 새로운 참여
Service->>Repository: save(new GroupUserV2)
activate Repository
Repository->>DB: insert
deactivate Repository
end
Service->>Repository: countByGroupIdAndStatus(groupId, ATTEND)
activate Repository
Repository->>DB: count query
DB-->>Repository: participantCount
deactivate Repository
Service->>Service: 용량 확인 및 상태 전환 (RECRUITING→FULL)
Service->>Service: MyMembership 구성
Service-->>Controller: AttendGroupV2Response
deactivate Service
Controller-->>Client: 200 OK + Response
deactivate Controller
end
rect rgb(240, 200, 220)
Note over Client,DB: 그룹 취소 흐름 (left)
Client->>Controller: POST /api/v2/groups/{groupId}/left
activate Controller
Controller->>Service: left(userId, groupId)
activate Service
Service->>Service: userId null 검증
Service->>DB: findGroupById(groupId)
DB-->>Service: GroupV2 entity
Service->>Service: 호스트 제한 확인
Service->>Repository: findByGroupIdAndUserId(groupId, userId)
Repository->>DB: query
DB-->>Repository: Optional<GroupUserV2>
alt ATTEND 상태 확인
Service->>Service: 상태가 ATTEND인지 검증
Service->>Repository: 상태 변경 (ATTEND→LEFT)
Service->>Repository: countByGroupIdAndStatus(groupId, ATTEND)
Repository->>DB: count query
DB-->>Repository: updatedCount
Service->>Service: 용량 확인 (FULL→RECRUITING 전환)
else ATTEND 아님
Service->>Service: 에러 반환
end
Service-->>Controller: AttendGroupV2Response
deactivate Service
Controller-->>Client: 200 OK + Response
deactivate Controller
end
Estimated code review effort🎯 3 (보통) | ⏱️ ~20-25 분 추가 주의 사항:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 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.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (1)
211-218:getReferenceById사용 시 사용자 존재 여부 미검증
getReferenceById는 DB 조회 없이 프록시 객체를 생성합니다. 해당userId가 실제로 존재하지 않는 경우, 저장 시점에 외래 키 제약 조건 위반이 발생할 수 있습니다.인증된 사용자만 이 API를 호출한다는 가정이 있다면 괜찮을 수 있으나, 명시적으로 검증하는 것이 더 안전합니다.
🔎 명시적 검증 추가
} else { // 최초 참석 생성 - groupUserV2 = GroupUserV2.create(group, userRepository.getReferenceById(userId), + User user = userRepository.findById(userId) + .orElseThrow(() -> new GroupException(GroupErrorCode.GROUP_USER_NOT_FOUND, userId)); + groupUserV2 = GroupUserV2.create(group, user, GroupUserV2Role.MEMBER);src/main/java/team/wego/wegobackend/group/v2/application/dto/common/MyMembership.java (1)
5-5: 사용되지 않는 import 제거 필요
GetGroupV2Response가 이 파일에서 사용되지 않습니다.🔎 수정 제안
-import team.wego.wegobackend.group.v2.application.dto.response.GetGroupV2Response;
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
src/main/java/team/wego/wegobackend/group/application/service/v1/GroupService.java(3 hunks)src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java(3 hunks)src/main/java/team/wego/wegobackend/group/v2/application/dto/common/MyMembership.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/dto/response/AttendGroupV2Response.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupV2Response.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java(4 hunks)src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2Repository.java(2 hunks)src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java(3 hunks)src/test/http/group/v2/V2-group-cancel.http(1 hunks)src/test/http/group/v2/v2-group-attend.http(1 hunks)src/test/http/group/v2/v2-group-get.http(1 hunks)
🔇 Additional comments (14)
src/test/http/group/v2/v2-group-get.http (1)
94-94: 동적 groupId 사용으로 테스트 유연성 향상하드코딩된 ID 대신 동적 변수를 사용하여 테스트 시나리오가 더 유연해졌습니다.
src/main/java/team/wego/wegobackend/group/application/service/v1/GroupService.java (3)
210-210: 에러 코드 네이밍 개선
GROUP_USER_NOT_FOUND로 변경하여 에러 코드가 더 명확하고 일관성 있게 되었습니다.
222-222: 에러 코드 네이밍 개선
GROUP_IS_FULL로 변경하여 그룹 정원 초과 상황을 더 직관적으로 표현합니다.
280-280: 에러 코드 네이밍 개선
GROUP_HOST_CANNOT_LEAVE로 변경하여 호스트가 자신의 그룹을 떠날 수 없다는 제약을 명확히 표현합니다.src/test/http/group/v2/v2-group-attend.http (1)
1-146: 포괄적인 참여 기능 테스트 시나리오성공 케이스와 예외 케이스(중복 참여, 호스트 자체 참여)를 모두 커버하는 잘 구성된 테스트입니다. 참여 상태 확인까지 포함되어 있어 기능 검증이 철저합니다.
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (2)
37-37: userId 직접 전달로 서비스 레이어 결합도 감소
CustomUserDetails객체 대신userId만 전달하여 서비스 레이어의 보안 관련 의존성을 제거했습니다. 깔끔한 레이어 분리입니다.
57-82: 모임 참여/취소 엔드포인트 구현 완료
/attend와/left엔드포인트가 RESTful하게 구현되었습니다. 빈 요청 본문을 받는 것은 의도된 설계로, 인증된 사용자와 경로 변수만으로 충분한 정보를 제공합니다.src/main/java/team/wego/wegobackend/group/v2/application/dto/response/AttendGroupV2Response.java (1)
1-31: 참여/취소 응답 DTO 잘 설계됨
AttendGroupV2Response가 참여/취소 작업 후 필요한 모든 정보(그룹 상태, 참가자 수, 내 멤버십 정보, 서버 시간)를 포함하고 있습니다. 공통MyMembership타입을 재사용하여 일관성도 확보했습니다.src/test/http/group/v2/V2-group-cancel.http (1)
1-186: 포괄적인 취소 기능 테스트 시나리오성공 케이스(멤버 취소)와 예외 케이스(중복 취소, 호스트 취소 시도)를 모두 검증하는 체계적인 테스트입니다. v2-group-attend.http와 함께 참여/취소 기능의 전체 흐름을 커버합니다.
src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2Repository.java (1)
21-23: 새로운 저장소 메서드는 정상 작동합니다
findByGroupIdAndUserId와countByGroupIdAndStatus메서드의 필드 매핑이 올바르게 구현되었습니다. GroupUserV2 엔티티가 group과 user 관계를 @manytoone으로 정의하고 있으며, groupId와 userId 직접 필드는 없어 Spring Data JPA가 자동으로 group.id와 user.id로 해석합니다. 기존 JPQL 쿼리(line 17)에서 사용하는gu.group.id패턴과 동일하게 동작하므로 별도의 수정이 필요하지 않습니다.src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (2)
178-195: 참여 전 검증 로직 LGTM
userIdnull 체크, 그룹 존재 여부, 호스트 참여 제한, 모집 상태 검증 등 필수 검증이 적절히 구현되어 있습니다.
241-278: 탈퇴 로직 구현 LGTM호스트 탈퇴 제한, 멤버십 존재 확인,
ATTEND상태에서만LEFT전이 허용, 그리고FULL→RECRUITING자동 복귀 로직이 잘 구현되어 있습니다.src/main/java/team/wego/wegobackend/group/v2/application/dto/common/MyMembership.java (1)
10-32: Record 구조 및 팩토리 메서드 LGTM
MyMembership레코드가 깔끔하게 정의되어 있고,from()팩토리 메서드가 스트림을 활용하여 사용자 멤버십을 적절히 매핑합니다. 비회원인 경우의 기본값 처리도 적절합니다.src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java (1)
43-45: 새 에러 코드 추가 LGTM
GROUP_HOST_CANNOT_ATTEND,GROUP_NOT_RECRUITING,GROUP_NOT_ATTEND_STATUS에러 코드가 참여/탈퇴 플로우에 맞게 적절히 정의되어 있습니다. 메시지가 명확하고 HTTP 상태 코드도 적절합니다.
| GROUP_STATUS_REQUIRED(HttpStatus.BAD_REQUEST, "모임: 모임 상태는 필수입니다."), | ||
| GROUP_STATUS_TRANSFER_IMPOSSIBLE(HttpStatus.BAD_REQUEST, "모임: 상태 전이가 불가능합니다. 현재 상태: %s, 요청한 상태: %s"); | ||
| GROUP_STATUS_TRANSFER_IMPOSSIBLE(HttpStatus.BAD_REQUEST, "모임: 상태 전이가 불가능합니다. 현재 상태: %s, 요청한 상태: %s"), | ||
| USER_ID_NULL(HttpStatus.NOT_FOUND, "모임: 회원 ID가 null 입니다."), |
There was a problem hiding this comment.
USER_ID_NULL HTTP 상태 코드 부적절
userId가 null인 경우는 잘못된 입력(클라이언트 오류)이므로 NOT_FOUND보다 BAD_REQUEST가 더 적절합니다. NOT_FOUND는 리소스가 존재하지 않을 때 사용하는 것이 RESTful 컨벤션입니다.
🔎 수정 제안
- USER_ID_NULL(HttpStatus.NOT_FOUND, "모임: 회원 ID가 null 입니다."),
+ USER_ID_NULL(HttpStatus.BAD_REQUEST, "모임: 회원 ID가 null 입니다."),📝 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.
| USER_ID_NULL(HttpStatus.NOT_FOUND, "모임: 회원 ID가 null 입니다."), | |
| USER_ID_NULL(HttpStatus.BAD_REQUEST, "모임: 회원 ID가 null 입니다."), |
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java
around line 42, the enum constant USER_ID_NULL currently uses
HttpStatus.NOT_FOUND but should use HttpStatus.BAD_REQUEST since a null userId
is a client input error; update the enum entry to use HttpStatus.BAD_REQUEST
instead of HttpStatus.NOT_FOUND so the API returns 400 for this validation
error.
| import team.wego.wegobackend.group.v2.application.dto.common.Address; | ||
| import team.wego.wegobackend.group.v2.application.dto.common.CreatedBy; | ||
| import team.wego.wegobackend.group.v2.application.dto.common.GroupImageItem; | ||
| import team.wego.wegobackend.group.v2.application.dto.common.MyMembership; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
MyMembership 외부 클래스로 추출하여 재사용성 향상
MyMembership을 공통 패키지로 분리하여 AttendGroupV2Response 등 다른 DTO에서도 재사용할 수 있게 되었습니다. 코드 중복을 줄이고 일관성을 높이는 좋은 리팩토링입니다.
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupV2Response.java
around line 8, update the class to use the extracted common MyMembership: ensure
the import points to
team.wego.wegobackend.group.v2.application.dto.common.MyMembership (or the
actual new common package path) and remove any local/duplicate MyMembership
class usages; then replace local definitions in other DTOs (e.g.,
AttendGroupV2Response) to reference this common MyMembership to eliminate
duplication and keep imports consistent.
There was a problem hiding this comment.
좋은것도 칭칭찬하려고 멘트 달아주네
There was a problem hiding this comment.
@LimdaeIl 네! 좋은 코드 개선 사항도 함께 공유하는 것이 중요하다고 생각합니다. 😊 코드 리뷰가 단순히 문제점만 지적하는 것이 아니라, 잘된 부분도 인정하고 팀 전체가 좋은 패턴을 배울 수 있도록 돕는 것이 목표입니다. 이번 MyMembership 리팩토링은 재사용성을 고려한 좋은 설계 결정이었습니다! 👍
| // 5) 정원 체크 수행. 재참여 포함해서 체크하는 게 안전 | ||
| long attendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId, | ||
| GroupUserV2Status.ATTEND); | ||
| if (attendCount > group.getMaxParticipants()) { | ||
| // 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외 | ||
| throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId); | ||
| } |
There was a problem hiding this comment.
정원 체크 타이밍 문제: 동시성 이슈 가능성
현재 정원 체크가 reAttend() 또는 save() 이후에 수행됩니다. 동시에 여러 사용자가 참여 요청을 보내면, 모두가 정원 체크를 통과한 뒤 예외가 발생하기 전까지 초과 참여가 허용될 수 있습니다.
더 안전한 방식은 참여 처리 전에 SELECT ... FOR UPDATE 또는 비관적 락을 사용하여 정원을 먼저 확인하는 것입니다.
🔎 개선 제안
+ // 정원 체크를 먼저 수행
+ long currentAttendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId,
+ GroupUserV2Status.ATTEND);
+ if (currentAttendCount >= group.getMaxParticipants()) {
+ throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId);
+ }
+
// 기존 멤버십 조회
GroupUserV2 groupUserV2 = groupUserV2Repository.findByGroupIdAndUserId(groupId, userId)
.orElse(null);락을 사용한 조회 메서드 추가도 고려해보세요:
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<GroupV2> findByIdWithLock(Long groupId);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java
around lines 220 to 226, the current capacity check runs after calling
reAttend()/save() which can allow race conditions; change the flow to acquire a
pessimistic write lock on the Group row before modifying membership, perform the
attendCount check while holding the lock, then call reAttend()/save() only if
the check passes. Add a repository method (e.g., findByIdWithLock(Long id)
annotated with @Lock(LockModeType.PESSIMISTIC_WRITE)), ensure the service method
executes in a transactional context, use that locked entity to re-check capacity
(including pending re-attendance) and then persist—this prevents concurrent
overbooking.
There was a problem hiding this comment.
이거 나중에 V3 로 나누거나 V2에서 찢을 생각이야.
There was a problem hiding this comment.
@LimdaeIl, 향후 리팩토링 계획을 이해했습니다! 동시성 이슈를 V3이나 V2 리팩토링 시 처리하시겠다는 거군요.
이 내용을 추적하기 위해 GitHub 이슈를 생성해드릴까요? 이슈에 현재 PR과 이 대화 링크를 포함하여 나중에 참고하기 편하도록 할 수 있습니다.
There was a problem hiding this comment.
Pull request overview
This pull request implements group attendance and cancellation functionality for V2 API endpoints, with proper handling for non-members and HOST users. The implementation includes business logic for joining and leaving groups, capacity management, and automatic status transitions between RECRUITING and FULL states.
Key Changes:
- Added two new endpoints:
/api/v2/groups/{groupId}/attendand/api/v2/groups/{groupId}/leftfor joining and leaving groups - Implemented business logic with validations for HOST restrictions, status checks, capacity management, and re-attendance scenarios
- Refactored
MyMembershipinto a common DTO and updated error codes for consistency across V1 and V2
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/test/http/group/v2/v2-group-get.http |
Updated test to use parameterized groupId |
src/test/http/group/v2/v2-group-attend.http |
Added comprehensive HTTP test scenarios for group attendance functionality |
src/test/http/group/v2/V2-group-cancel.http |
Added comprehensive HTTP test scenarios for group cancellation functionality |
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java |
Added attend and left endpoints with proper authentication and parameter handling |
src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2Repository.java |
Added query methods for finding group users and counting attendees by status |
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java |
Implemented attend and left business logic with validations, capacity checks, and status transitions |
src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupV2Response.java |
Moved MyMembership record to common package for reusability |
src/main/java/team/wego/wegobackend/group/v2/application/dto/response/AttendGroupV2Response.java |
Created response DTO for attendance operations with group status and participant count |
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/MyMembership.java |
Extracted MyMembership as a common DTO shared across multiple responses |
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java |
Added new error codes and renamed existing ones for better clarity and V2 support |
src/main/java/team/wego/wegobackend/group/application/service/v1/GroupService.java |
Updated to use renamed error codes for consistency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (group.getStatus() != GroupV2Status.RECRUITING) { | ||
| throw new GroupException(GroupErrorCode.GROUP_NOT_RECRUITING, group.getStatus().name()); | ||
| } |
There was a problem hiding this comment.
The attendance validation only checks if the group status is RECRUITING, but does not prevent attendance when the group status is FULL. This could allow users to join a full group if they were previously LEFT or KICKED. The validation should also reject attendance for FULL status groups, or alternatively, allow attendance to FULL groups with capacity checks.
| import org.springframework.web.bind.annotation.RestController; | ||
| import team.wego.wegobackend.common.response.ApiResponse; | ||
| import team.wego.wegobackend.common.security.CustomUserDetails; | ||
| import team.wego.wegobackend.group.application.dto.v1.response.GetGroupResponse; |
There was a problem hiding this comment.
The import team.wego.wegobackend.group.application.dto.v1.response.GetGroupResponse is not used in this file. This import should be removed to keep the code clean.
| import team.wego.wegobackend.group.application.dto.v1.response.GetGroupResponse; |
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import team.wego.wegobackend.group.v2.application.dto.response.GetGroupV2Response; |
There was a problem hiding this comment.
The import team.wego.wegobackend.group.v2.application.dto.response.GetGroupV2Response is not used in this file. This import should be removed to keep the code clean.
| import team.wego.wegobackend.group.v2.application.dto.response.GetGroupV2Response; |
| // 5) 정원 체크 수행. 재참여 포함해서 체크하는 게 안전 | ||
| long attendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId, | ||
| GroupUserV2Status.ATTEND); | ||
| if (attendCount > group.getMaxParticipants()) { | ||
| // 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외 | ||
| throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId); | ||
| } |
There was a problem hiding this comment.
There is a potential race condition between checking capacity and saving the attendance. If two users attempt to join simultaneously when there is only one spot left, both could pass the capacity check and exceed maxParticipants. Consider using pessimistic locking on the group entity or implementing a database-level constraint to prevent this race condition.
| @Transactional | ||
| public AttendGroupV2Response attend(Long userId, Long groupId) { | ||
| if (userId == null) { | ||
| throw new GroupException(GroupErrorCode.USER_ID_NULL); | ||
| } | ||
|
|
||
| GroupV2 group = groupV2Repository.findById(groupId) | ||
| .orElseThrow( | ||
| () -> new GroupException(GroupErrorCode.GROUP_NOT_FOUND_BY_ID, groupId)); | ||
|
|
||
| if (group.getHost().getId().equals(userId)) { | ||
| throw new GroupException(GroupErrorCode.GROUP_HOST_CANNOT_ATTEND); | ||
| } | ||
|
|
||
| // 모임 상태 체크 | ||
| if (group.getStatus() != GroupV2Status.RECRUITING) { | ||
| throw new GroupException(GroupErrorCode.GROUP_NOT_RECRUITING, group.getStatus().name()); | ||
| } | ||
|
|
||
| // 기존 멤버십 조회 | ||
| GroupUserV2 groupUserV2 = groupUserV2Repository.findByGroupIdAndUserId(groupId, userId) | ||
| .orElse(null); | ||
|
|
||
| if (groupUserV2 != null) { | ||
| if (groupUserV2.getStatus() == GroupUserV2Status.BANNED) { | ||
| throw new GroupException(GroupErrorCode.GROUP_BANNED_USER); | ||
| } | ||
| // 이미 참석중이면 충돌 | ||
| if (groupUserV2.getStatus() == GroupUserV2Status.ATTEND) { | ||
| throw new GroupException(GroupErrorCode.ALREADY_ATTEND_GROUP, groupId, userId); | ||
| } | ||
| // LEFT/KICKED면 재참여 허용 | ||
| groupUserV2.reAttend(); | ||
| } else { | ||
| // 최초 참석 생성 | ||
| groupUserV2 = GroupUserV2.create(group, userRepository.getReferenceById(userId), | ||
| GroupUserV2Role.MEMBER); | ||
| // create에서 group.addUser로 연관관계 맞추는 구조. | ||
| // group은 영속 상태여야 한다. (위에서 findById 했으니 ok) | ||
| groupUserV2Repository.save(groupUserV2); | ||
| } | ||
|
|
||
| // 5) 정원 체크 수행. 재참여 포함해서 체크하는 게 안전 | ||
| long attendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId, | ||
| GroupUserV2Status.ATTEND); | ||
| if (attendCount > group.getMaxParticipants()) { | ||
| // 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외 | ||
| throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId); | ||
| } | ||
|
|
||
| // FULL 자동 전환 | ||
| if (attendCount == group.getMaxParticipants() | ||
| && group.getStatus() == GroupV2Status.RECRUITING) { | ||
| group.changeStatus(GroupV2Status.FULL); | ||
| } | ||
|
|
||
| // 내 멤버십 + 최신 카운트 + 모임 상태 응답 | ||
| MyMembership membership = | ||
| MyMembership.from(List.of(groupUserV2), userId); | ||
|
|
||
| return AttendGroupV2Response.of(group, attendCount, membership); | ||
| } |
There was a problem hiding this comment.
The new attend() method lacks unit test coverage. Given that v1 GroupService has corresponding test coverage, consider adding unit tests for this method to verify business logic including edge cases like capacity checks, status validations, and concurrent access scenarios.
| @Transactional | ||
| public AttendGroupV2Response left(Long userId, Long groupId) { | ||
| if (userId == null) { | ||
| throw new GroupException(GroupErrorCode.USER_ID_NULL); | ||
| } | ||
|
|
||
| GroupV2 group = groupV2Repository.findById(groupId) | ||
| .orElseThrow( | ||
| () -> new GroupException(GroupErrorCode.GROUP_NOT_FOUND_BY_ID, groupId)); | ||
|
|
||
| if (group.getHost().getId().equals(userId)) { | ||
| throw new GroupException(GroupErrorCode.GROUP_HOST_CANNOT_LEAVE, groupId, userId); | ||
| } | ||
|
|
||
| GroupUserV2 groupUserV2 = groupUserV2Repository.findByGroupIdAndUserId(groupId, userId) | ||
| .orElseThrow(() -> new GroupException(GroupErrorCode.GROUP_USER_NOT_FOUND, userId)); | ||
|
|
||
| // ATTEND만 LEFT 가능 | ||
| if (groupUserV2.getStatus() != GroupUserV2Status.ATTEND) { | ||
| throw new GroupException(GroupErrorCode.GROUP_NOT_ATTEND_STATUS); | ||
| } | ||
|
|
||
| groupUserV2.leave(); | ||
|
|
||
| long attendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId, | ||
| GroupUserV2Status.ATTEND); | ||
|
|
||
| // FULL -> RECRUITING 자동 복귀(선택) | ||
| if (group.getStatus() == GroupV2Status.FULL && attendCount < group.getMaxParticipants()) { | ||
| group.changeStatus(GroupV2Status.RECRUITING); | ||
| } | ||
|
|
||
| // 응답 | ||
| MyMembership membership = | ||
| MyMembership.from(List.of(groupUserV2), userId); | ||
|
|
||
| return AttendGroupV2Response.of(group, attendCount, membership); | ||
| } |
There was a problem hiding this comment.
The new left() method lacks unit test coverage. Given that v1 GroupService has corresponding test coverage, consider adding unit tests for this method to verify business logic including edge cases like HOST restrictions, status validations, and automatic status transitions.
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
비회원, HOST 를 고려해서 모임 참석, 취소를 구현했습니다.
🔍 관련 이슈
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.