Skip to content

[FEAT] 모임 승인제 요청에 따른 리팩토링#149

Merged
LimdaeIl merged 3 commits intomainfrom
feat/group-v2-attendence
Dec 22, 2025
Merged

[FEAT] 모임 승인제 요청에 따른 리팩토링#149
LimdaeIl merged 3 commits intomainfrom
feat/group-v2-attendence

Conversation

@LimdaeIl
Copy link
Collaborator

@LimdaeIl LimdaeIl commented Dec 22, 2025

📝 Pull Request

📌 PR 종류

해당하는 항목에 체크해주세요.

  • 기능 추가 (Feature)
  • 버그 수정 (Fix)
  • 문서 수정 (Docs)
  • 코드 리팩터링 (Refactor)
  • 테스트 추가 (Test)
  • 기타 변경 (Chore)

✨ 변경 내용

  • 모임 승인제 기능 추가 -> 현재는 attend, left
  • 모임 상태 추가 PENDING REJECTED CANCELLED
  • 모임 응답 필드 추가 joinPolicy

🔍 관련 이슈

🧪 테스트

변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.

  • 유닛 테스트 추가 / 수정
  • 통합 테스트 검증
  • 수동 테스트 완료

🚨 확인해야 할 사항 (Checklist)

PR을 제출하기 전에 아래 항목들을 확인해주세요.

  • 코드 포매팅 완료
  • 불필요한 파일/코드 제거
  • 로직 검증 완료
  • 프로젝트 빌드 성공
  • 린트/정적 분석 통과 (해당 시)

🙋 기타 참고 사항

리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 모임 가입 정책 추가: 즉시 가입, 승인 필요, 초대 방식 3가지 옵션 지원
    • 모임 멤버십 상태 확장: 대기, 거절, 취소 상태 추가
    • 개선된 모임 참여/탈출 워크플로우 제공
  • 개선 사항

    • 모임 가입/탈출 시나리오에 대한 상세한 오류 메시지 추강
    • 무효한 가입 정책, 중복 신청, 상태 불일치 등에 대한 검증 강화

✏️ Tip: You can customize this high-level summary in your review settings.

그룹 출석 기능을 도입하여 사용자가 그룹에 참여하고 나갈 수 있도록 합니다.
다양한 출석 관련 시나리오에 대한 새로운 오류 코드를 추가합니다.
유연한 그룹 참여를 위해 참여 정책(즉시 참여, 승인 필요)을 통합합니다.
참여 정책 정보를 포함하도록 그룹 목록 및 상세 정보 응답을 재구성합니다.
출석 및 퇴장 작업을 처리하는 출석 관리 전용 서비스를 구현합니다.
@LimdaeIl LimdaeIl self-assigned this Dec 22, 2025
Copilot AI review requested due to automatic review settings December 22, 2025 13:47
@LimdaeIl LimdaeIl added the ✨enhancement New feature or request label Dec 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

개요

그룹 가입 정책(INSTANT, APPROVAL_REQUIRED, INVITE_ONLY)을 도입하고, 멤버십 상태 관리 개선, 그룹 참여/퇴출 로직을 전용 서비스로 분리했습니다. 에러 핸들링 강화 및 다중 DTO 확장을 통해 정책 정보를 응답에 포함시켰습니다.

변경사항

집단 / 파일(들) 변경 요약
에러 코드
GroupErrorCode.java
GROUP_CANNOT_LEAVE_IN_STATUS, GROUP_USER_STATUS_NOT_ALLOWED_TO_LEAVE, GROUP_NOT_PENDING_STATUS, ALREADY_LEFT_GROUP, ALREADY_CANCELLED_JOIN_REQUEST, GROUP_KICKED_USER, GROUP_REJECTED_USER, INVALID_JOIN_POLICY, JOIN_POLICY_NULL, ALREADY_REQUESTED_TO_JOIN 등 10개의 새로운 예외 상수 추가
도메인 정책 & 상태
GroupV2JoinPolicy.java, GroupUserV2Status.java, GroupV2Status.java
새로운 GroupV2JoinPolicy 열거형(INSTANT, APPROVAL_REQUIRED, INVITE_ONLY) 추가; GroupUserV2Status에 PENDING, REJECTED, CANCELLED 상수 추가; GroupV2Status에 canLeaveOrCancel() 메서드 추가
도메인 엔터티
GroupV2.java, GroupUserV2.java
GroupV2에 joinPolicy 필드 및 create() 팩토리 메서드 업데이트; GroupUserV2에 createPending(), requestJoin(), leaveOrCancel(), cancelRequest() 메서드 추가
응답 DTO들
AttendanceGroupV2Response.java, CreateGroupV2Response.java, GetGroupV2Response.java, GetGroupListV2Response.java, GetMyGroupListV2Response.java, UpdateGroupV2Response.java
모든 응답 DTO에 joinPolicy 필드 추가 및 팩토리 메서드 업데이트
요청 DTO
CreateGroupV2Request.java
joinPolicy 필드(@NotNull 어노테이션) 추가
쿼리 및 프로젝션
GroupV2QueryRepositoryImpl.java, GroupListRow.java, MyGroupListRow.java
쿼리 프로젝션 및 생성자에 joinPolicy 필드 추가
서비스 - 분리 & 이전
GroupV2Service.java, GroupV2AttendanceService.java
GroupV2Service에서 attend(), left() 메서드 제거; 새로운 GroupV2AttendanceService 생성으로 attendance 로직 분리 (정책 적용, 상태 전이, 용량 제약 관리)
서비스 - 업데이트
GroupV2UpdateService.java, GroupMyGetV2Service.java
UpdateGroupV2Response 구성 시 joinPolicy 추가; MyGroupListRow 매핑에서 joinPolicy() 포함
프레젠테이션
GroupV2Controller.java, GroupV2ControllerDocs.java
GroupV2AttendanceService 의존성 주입; attend/left 메서드 반환 타입을 AttendanceGroupV2Response로 변경
HTTP 테스트
v2-group-attend-instant.http, v2-group-attend-approval-required.http, v2-group-left.http, v2-group-create.http, v2-group-delete.http, v2-group-get*.http, v2-group-update.http
joinPolicy: "INSTANT" 필드를 생성 요청에 추가; 신규 테스트 시나리오(INSTANT, APPROVAL_REQUIRED 정책) 추가; 기존 테스트 정리 및 재구성

시퀀스 다이어그램

sequenceDiagram
    participant Client
    participant Controller as GroupV2Controller
    participant AttendService as GroupV2AttendanceService
    participant GroupRepo as GroupV2Repository
    participant UserRepo as GroupUserV2Repository
    participant Domain as GroupUserV2 Domain

    Client->>Controller: attend(userId, groupId)
    activate Controller
    Controller->>AttendService: attend(userId, groupId)
    activate AttendService
    
    AttendService->>GroupRepo: findById(groupId)
    activate GroupRepo
    GroupRepo-->>AttendService: group
    deactivate GroupRepo
    
    AttendService->>AttendService: 검증: 호스트 제약, 그룹 상태, 용량
    
    AttendService->>UserRepo: findByGroupIdAndUserId(groupId, userId)
    activate UserRepo
    UserRepo-->>AttendService: existingMembership (optional)
    deactivate UserRepo
    
    alt 기존 멤버십 없음
        AttendService->>Domain: GroupUserV2.createPending(group, user)
        activate Domain
        Domain->>Domain: 가입정책 확인
        alt 정책 = INSTANT
            Domain->>Domain: status = ATTEND
        else 정책 = APPROVAL_REQUIRED
            Domain->>Domain: status = PENDING
        end
        Domain-->>AttendService: newMembership
        deactivate Domain
    else 기존 멤버십 있음
        AttendService->>Domain: existingMembership.requestJoin()
        activate Domain
        Domain->>Domain: 상태 전이 (CANCELLED/LEFT → PENDING)
        Domain-->>AttendService: void
        deactivate Domain
    end
    
    AttendService->>AttendService: ATTEND 인원 재계산, FULL 상태 업데이트
    AttendService->>GroupRepo: save(group)
    
    AttendService-->>Controller: AttendanceGroupV2Response
    deactivate AttendService
    
    Controller-->>Client: ApiResponse<AttendanceGroupV2Response>
    deactivate Controller

    Client->>Controller: left(userId, groupId)
    activate Controller
    Controller->>AttendService: left(userId, groupId)
    activate AttendService
    
    AttendService->>GroupRepo: findById(groupId)
    AttendService->>UserRepo: findByGroupIdAndUserId(groupId, userId)
    
    AttendService->>Domain: membership.leaveOrCancel()
    activate Domain
    Domain->>Domain: 상태 기반 처리 (ATTEND → LEFT, PENDING → CANCELLED)
    Domain-->>AttendService: void
    deactivate Domain
    
    AttendService->>AttendService: ATTEND 인원 재계산, RECRUITING 상태 복구
    AttendService->>GroupRepo: save(group)
    
    AttendService-->>Controller: AttendanceGroupV2Response
    deactivate AttendService
    
    Controller-->>Client: ApiResponse<AttendanceGroupV2Response>
    deactivate Controller
Loading

예상 코드 리뷰 난이도

🎯 4 (복잡함) | ⏱️ ~50분

주의 깊게 검토할 영역:

  • GroupV2AttendanceService: 가입 정책(INSTANT vs APPROVAL_REQUIRED) 적용, 멤버십 상태 전이(PENDING→ATTEND, CANCELLED→PENDING 등), 용량 관리 및 그룹 상태(RECRUITING↔FULL) 업데이트 로직의 정확성 검증 필요
  • GroupUserV2 도메인 메서드들: createPending(), requestJoin(), leaveOrCancel(), cancelRequest()의 상태 전이 규칙 및 예외 조건 검증 필요
  • GroupV2JoinPolicy 정책 처리: 세 가지 정책(INSTANT, APPROVAL_REQUIRED, INVITE_ONLY)에 대한 분기 로직이 모든 DTO 및 서비스에서 일관성 있게 적용되었는지 확인
  • 기존 GroupV2Service 제거된 메서드들: attend(), left() 메서드 제거 후 모든 호출 지점이 새로운 GroupV2AttendanceService로 올바르게 마이그레이션되었는지 확인

관련 가능성 있는 PR들

마음 전하는 시

🐰 정책을 들고 온 우리의 변화,
INSTANT, APPROVAL로 유연하게 변해,
상태의 춤을 도메인이 춘다며,
LEFT와 PENDING 우아히 뜨고,
새로운 서비스 손잡고 나아간다 ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning PR은 #148과 연결되었으나, 링크된 이슈 #12, #5는 환경 설정 및 배포 관련으로 본 PR의 모임 승인제 기능과 무관합니다. 정확한 관련 이슈 #148을 링크하거나, 링크된 이슈가 실제 PR 요구사항과 일치하는지 확인하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Pull request 제목은 모임 승인제 기능 리팩토링이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Out of Scope Changes check ✅ Passed PR의 모든 변경사항이 모임 승인제 기능(GroupUserV2 상태 추가, joinPolicy 필드, 승인/탈퇴 로직 등)에 집중되어 있으며, 범위를 벗어난 변경사항은 없습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/group-v2-attendence

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 and usage tips.

@LimdaeIl LimdaeIl moved this from Backlog to In progress in WeGo-Together Backend Dec 22, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (6)
src/test/http/group/v2/v2-group-attend-approval-required.http (1)

147-149: 선택적 테스트에 응답 검증 추가를 고려해 주세요.

Line 147의 상세 조회 테스트에 응답 검증 블록이 없습니다. MEMBER1의 상태가 PENDING인지 확인하는 assertion을 추가하면 테스트 완성도가 높아집니다.

🔎 응답 검증 블록 제안
 ### 7. (선택) 모임 상세 조회 - MEMBER1 토큰으로 내 상태 확인
 GET http://localhost:8080/api/v2/groups/{{groupId_approvalTest}}
 Authorization: Bearer {{member1AccessToken}}
+
+> {%
+  client.test("응답 성공", () => client.assert(response.status === 200));
+  // 필요시 멤버 상태 검증 추가
+%}
src/test/http/group/v2/v2-group-update.http (1)

353-354: 테스트 데이터의 날짜가 과거입니다.

startTime2025-12-30으로 설정되어 있는데, 현재 날짜(2025년 12월)와 가까워 곧 과거 날짜가 됩니다. 모임 생성/수정 시 과거 날짜 검증 로직이 있다면 테스트가 실패할 수 있습니다. 미래 날짜(예: 2026년)로 변경하는 것이 좋습니다.

🔎 날짜 수정 제안
-  "startTime": "2025-12-30T19:00:00",
-  "endTime": "2025-12-30T21:00:00",
+  "startTime": "2026-12-30T19:00:00",
+  "endTime": "2026-12-30T21:00:00",
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2UpdateService.java (1)

127-151: 미사용 메서드가 있습니다.

defaultLogoItem() 메서드가 정의되어 있지만 현재 어디에서도 호출되지 않습니다. Lines 107-109에서 imageItems가 비어있을 때 기본 이미지를 설정하려는 의도로 보이지만, 실제로는 빈 ArrayList만 생성하고 있습니다.

의도된 동작인지 확인이 필요합니다:

  1. 기본 이미지가 필요 없다면 이 메서드를 제거
  2. 기본 이미지가 필요하다면 Lines 107-109 수정
🔎 기본 이미지 적용 예시
         // 이미지가 0개면 기본 이미지(variants 2개) 1장 내려주기
         if (imageItems.isEmpty()) {
-            imageItems = new ArrayList<>();
+            imageItems = List.of(defaultLogoItem());
         }
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java (1)

90-92: getReferenceById 사용 시 사용자 존재 여부 미검증

userRepository.getReferenceById(userId)는 프록시만 반환하므로, 존재하지 않는 사용자 ID가 전달될 경우 나중에 EntityNotFoundException이 발생합니다. 입력 검증 단계에서 사용자 존재 여부를 확인하는 것이 좋습니다.

src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (1)

90-100: left() 엔드포인트 업데이트 적절함

attend()와 동일한 패턴으로 새 서비스로 위임합니다.

참고: HTTP 메서드가 POST로 되어 있는데, left 액션은 상태 변경이므로 DELETEPATCH가 RESTful 관례에 더 부합할 수 있습니다. 하지만 기존 API 계약이 있다면 유지해도 무방합니다.

src/test/http/group/v2/V2-group-left.http (1)

58-81: joinPolicy 필드 누락 (선택사항)

모임 생성 요청에 joinPolicy 필드가 포함되지 않았습니다. 기본값(INSTANT)이 적용되어 테스트는 동작하지만, 명시적으로 추가하면 테스트 의도가 더 명확해집니다.

   "description": "left() 성공/중복 left/HOST left 불가를 테스트합니다.",
   "maxParticipants": 5,
+  "joinPolicy": "INSTANT",
   "images": [
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae6e3ff and dca2088.

📒 Files selected for processing (34)
  • src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/common/MyMembership.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupV2Request.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/AttendanceGroupV2Response.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/CreateGroupV2Response.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupListV2Response.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupV2Response.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetMyGroupListV2Response.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/UpdateGroupV2Response.java
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupMyGetV2Service.java
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2UpdateService.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2Status.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2JoinPolicy.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2Status.java
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupV2QueryRepositoryImpl.java
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/GroupListRow.java
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/MyGroupListRow.java
  • src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java
  • src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2ControllerDocs.java
  • src/test/http/group/group-attend.http
  • src/test/http/group/v2/V2-group-left.http
  • src/test/http/group/v2/v2-group-attend-approval-required.http
  • src/test/http/group/v2/v2-group-attend-instant.http
  • src/test/http/group/v2/v2-group-attend.http
  • src/test/http/group/v2/v2-group-create.http
  • src/test/http/group/v2/v2-group-delete.http
  • src/test/http/group/v2/v2-group-get-list.http
  • src/test/http/group/v2/v2-group-get-me.http
  • src/test/http/group/v2/v2-group-get.http
  • src/test/http/group/v2/v2-group-update.http
💤 Files with no reviewable changes (1)
  • src/test/http/group/v2/v2-group-attend.http
🔇 Additional comments (42)
src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2Status.java (1)

3-11: LGTM! 승인제 기능을 위한 상태값 추가가 적절합니다.

PENDING, REJECTED, CANCELLED 상태가 추가되어 승인 기반 참여 흐름을 지원합니다. 상태 정의가 명확하고 완전합니다.

src/test/http/group/v2/v2-group-update.http (1)

265-381: 이미지 업로드 및 PATCH 테스트 예제 추가가 잘 되었습니다.

다양한 필드와 이미지 교체 시나리오를 커버하는 종합적인 테스트 흐름입니다. assertion을 통한 검증도 충실합니다.

src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2.java (3)

129-136: createPending() 팩토리 메서드가 적절하게 구현되었습니다.

승인제 모임의 가입 요청을 위한 PENDING 상태 생성이 잘 캡슐화되어 있습니다.


148-180: leaveOrCancel() 상태 기반 핸들링이 잘 구현되었습니다.

switch 표현식을 사용하여 각 상태별 처리가 명확하게 분리되어 있고, 모든 케이스에 대해 적절한 예외를 던지고 있습니다. default 케이스로 방어적 처리도 되어 있습니다.


85-92: leave() 메서드의 예외 처리 개선이 좋습니다.

기존 silent failure 대신 명시적 예외를 던지도록 변경되어 디버깅과 오류 추적이 용이해졌습니다.

src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java (2)

11-39: 에러 코드 추가가 잘 설계되었습니다.

각 상태 전이 시나리오에 대해 적절한 HTTP 상태 코드(CONFLICT, FORBIDDEN, BAD_REQUEST)가 사용되었고, 메시지 템플릿에 컨텍스트 정보(%s 포맷)가 포함되어 있어 디버깅에 유용합니다.


86-86: ALREADY_REQUESTED_TO_JOIN 에러 코드 추가 확인.

중복 가입 요청에 대한 에러 코드가 적절하게 추가되었습니다. 이 에러 코드가 GroupUserV2.requestJoin() 메서드에서 활용되어야 합니다.

src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/MyGroupListRow.java (1)

6-6: 변경 사항 확인 완료.

GroupV2JoinPolicy 타입의 joinPolicy 필드가 레코드에 추가되어 프로젝션 레이어를 통해 참여 정책 정보가 전달됩니다. 구조적으로 문제없습니다.

Also applies to: 12-12

src/test/http/group/v2/v2-group-delete.http (1)

60-60: 테스트 페이로드 업데이트 확인.

삭제 테스트에 joinPolicy: "INSTANT" 필드가 추가되어 새로운 API 요구사항을 반영합니다. 삭제 로직은 참여 정책과 무관하므로 이 테스트에서 단일 정책만 사용하는 것은 적절합니다.

Also applies to: 155-155

src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/GroupListRow.java (1)

4-4: 변경 사항 확인 완료.

GroupListRow 프로젝션에 joinPolicy 필드가 추가되어 MyGroupListRow와 일관된 구조를 유지합니다.

Also applies to: 10-10

src/test/http/group/v2/v2-group-get.http (1)

64-64: 테스트 페이로드 업데이트 확인.

조회 테스트에 joinPolicy 필드가 추가되어 API 변경사항을 반영합니다.

Also applies to: 105-105

src/test/http/group/v2/v2-group-create.http (1)

62-62: 테스트 페이로드에 joinPolicy 필드 추가 확인.

모든 모임 생성 테스트에 joinPolicy: "INSTANT" 필드가 추가되어 새로운 API 요구사항을 반영합니다.

Also applies to: 129-129, 190-190, 281-281

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupListV2Response.java (2)

5-5: DTO에 joinPolicy 필드 추가 확인.

GroupV2JoinPolicy 필드가 응답 DTO에 추가되고 팩토리 메서드를 통해 올바르게 전파됩니다. 구조적으로 문제없습니다.

Also applies to: 20-20, 50-50, 77-77


69-72: joinable 계산 로직에 joinPolicy 고려 필요 여부 확인.

현재 joinable 플래그는 statusremainingSeats만 기반으로 계산됩니다. 그러나 joinPolicyAPPROVAL_REQUIRED 또는 INVITE_ONLY인 경우, 즉시 참여가 불가능하므로 이 로직에 joinPolicy도 반영해야 할 수 있습니다.

예를 들어:

  • INSTANT: 현재 로직 유지
  • APPROVAL_REQUIRED: joinable은 true지만 승인 대기 상태 표시 필요
  • INVITE_ONLY: joinable은 false여야 할 수 있음

joinPolicy에 따른 비즈니스 요구사항을 확인하고, 필요시 로직을 조정하세요.

src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2Status.java (1)

27-30: CLOSED 상태에서 퇴장 허용 여부 확인 필요.

canLeaveOrCancel() 메서드가 CLOSED 상태에서도 true를 반환합니다. CLOSED는 "모집마감 (더 이상 참가 불가)" 상태인데, 이미 참가한 사용자가 퇴장할 수 있어야 하는지 비즈니스 요구사항을 확인하세요.

일반적으로:

  • 모집이 마감되었다면 신규 참가는 불가하지만, 기존 참가자의 퇴장은 허용될 수 있습니다.
  • 또는 모집 마감 후에는 참가자 변경을 모두 불허할 수도 있습니다.

비즈니스 정책에 따라 로직을 확인하고, 주석에 정책을 명시하는 것을 권장합니다.

src/test/http/group/v2/v2-group-get-list.http (1)

63-63: 테스트 페이로드 업데이트 확인.

목록 조회 테스트에 joinPolicy 필드가 추가되어 API 변경사항을 반영합니다.

src/test/http/group/v2/v2-group-get-me.http (1)

60-60: 테스트 페이로드가 새로운 joinPolicy 필드를 올바르게 반영하고 있습니다.

INSTANT 정책으로 테스트하는 것이 적절합니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupMyGetV2Service.java (1)

99-136: 변수명 리팩토링과 joinPolicy 필드 추가가 잘 되었습니다.

r에서 myGroupListRow로 변수명을 변경하여 가독성이 크게 향상되었으며, joinPolicy 필드가 모든 응답 매핑에 일관되게 추가되었습니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/common/MyMembership.java (1)

31-44: 새로운 팩토리 메서드가 잘 구현되었습니다.

null 체크 로직이 기존 from(List, Long) 메서드(line 28)와 일관된 패턴을 따르고 있으며, GroupUserV2에서 MyMembership으로의 매핑이 명확합니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/UpdateGroupV2Response.java (1)

8-24: UpdateGroupV2Response에 joinPolicy 필드가 올바르게 추가되었습니다.

다른 V2 응답 DTO들과 일관된 구조를 유지하고 있습니다.

src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupV2QueryRepositoryImpl.java (3)

74-117: QueryDSL 프로젝션과 GROUP BY 절이 올바르게 업데이트되었습니다.

Line 78에서 group.joinPolicy를 SELECT 프로젝션에 추가하고, Line 101에서 groupBy 절에도 동일하게 추가하여 SQL GROUP BY 오류를 방지했습니다. 이는 QueryDSL에서 집계 쿼리를 작성할 때 필수적인 패턴입니다.


223-289: fetchMyGroupRows 메서드의 프로젝션이 일관되게 업데이트되었습니다.

Line 228과 Line 264에서 group.joinPolicy가 프로젝션과 groupBy 절에 모두 올바르게 추가되었습니다.


319-360: fetchMyPostGroupRows 메서드의 프로젝션이 올바르게 업데이트되었습니다.

Line 322와 Line 347에서 group.joinPolicy가 일관되게 추가되었습니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/CreateGroupV2Response.java (1)

13-71: CreateGroupV2Response에 joinPolicy 필드가 올바르게 추가되었습니다.

Line 21에서 필드를 선언하고 Line 56에서 group.getJoinPolicy()를 통해 도메인 엔티티로부터 값을 가져오는 패턴이 적절합니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetGroupV2Response.java (1)

24-24: LGTM!

joinPolicy 필드가 응답 DTO에 올바르게 추가되었습니다. 필드 위치도 title 다음, status 앞으로 논리적인 순서입니다.

Also applies to: 89-89

src/test/http/group/group-attend.http (1)

1-145: LGTM!

테스트 케이스 구조가 명확하게 재정리되었습니다. 단계별 넘버링(0, 0-1, 1-1...)이 플로우를 이해하기 쉽게 만들어줍니다. 주요 시나리오(성공적인 참여, 중복 참여 예외, 호스트 자가 참여 예외)가 잘 커버되어 있습니다.

src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2ControllerDocs.java (1)

17-17: LGTM!

AttendanceGroupV2Response로의 타입 변경이 일관되게 적용되었습니다. attendleft 엔드포인트가 동일한 응답 타입을 반환하는 것이 적절합니다.

Also applies to: 54-54, 64-64

src/test/http/group/v2/v2-group-attend-instant.http (1)

1-195: LGTM! 포괄적인 테스트 시나리오입니다.

INSTANT 정책에 대한 출석 플로우가 잘 구성되어 있습니다:

  • 성공 케이스: 참여, 나가기, 재참여
  • 예외 케이스: 중복 참여, 중복 나가기, 호스트 자가 참여

APPROVAL_REQUIREDINVITE_ONLY 정책에 대한 유사한 테스트 파일도 추후 추가하면 좋겠습니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2UpdateService.java (1)

111-124: LGTM!

joinPolicy 필드가 UpdateGroupV2Response에 올바르게 전달되었습니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetMyGroupListV2Response.java (1)

22-22: LGTM!

joinPolicy 필드가 Item 레코드에 일관되게 추가되었습니다. 다른 응답 DTO들과 필드 순서가 동일하여 API 응답의 일관성이 유지됩니다.

Also applies to: 44-44, 66-66

src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2.java (2)

68-71: LGTM!

joinPolicy 필드가 올바르게 매핑되었습니다:

  • EnumType.STRING 사용으로 DB 가독성과 마이그레이션 안전성 확보
  • nullable = false로 필수 필드 보장
  • length = 30은 현재 enum 값들(INSTANT, APPROVAL_REQUIRED, INVITE_ONLY)을 충분히 수용

88-99: 기본값 로직이 적절합니다.

joinPolicynull일 경우 INSTANT로 기본값 설정하는 방어적 프로그래밍이 적용되어 있습니다. CreateGroupV2Request에서 @NotNull 검증이 있지만, 엔티티 레벨에서의 방어 로직은 직접 엔티티 생성 시나리오에 대비하여 유용합니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/response/AttendanceGroupV2Response.java (1)

9-33: LGTM!

AttendanceGroupV2Response로의 리네이밍이 적절합니다. "Attendance"가 참여(attend)와 퇴장(left) 양쪽 동작을 포괄하는 명칭입니다. joinPolicy 필드 추가와 팩토리 메서드 업데이트가 일관되게 적용되었습니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java (2)

125-132: APPROVAL_REQUIRED 브랜치에서 정원 체크 로직 검토 필요

APPROVAL_REQUIRED 정책에서는 requestJoin()PENDING 상태로 변경합니다. PENDING 상태는 attendCount에 포함되지 않으므로, Line 129-131의 정원 초과 체크는 실제로 트리거되지 않습니다.

주석에 "재참여 포함해서 체크"라고 되어 있지만, requestJoin()ATTEND가 아닌 PENDING으로 변경하므로 이 체크는 의미가 없어 보입니다. 승인 시점에서 정원 체크가 필요합니다.


148-193: left() 메서드 로직 적절함

left() 메서드는 HOST 나가기 방지, 상태 기반 검증, 도메인 위임(leaveOrCancel()), FULL → RECRUITING 자동 복귀 등 필요한 로직이 잘 구현되어 있습니다.

src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (2)

143-143: joinPolicy 필드 추가 적절함

모임 목록 응답에 joinPolicy가 포함되어, 클라이언트가 참여 정책을 미리 확인할 수 있습니다.


186-195: 모임 생성 시 joinPolicy 전달 구조 적절함

request.joinPolicy()를 도메인 레이어로 전달하고, null 처리는 도메인에서 담당하는 구조가 깔끔합니다.

src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (2)

47-47: 새로운 서비스 의존성 주입 적절함

GroupV2AttendanceService 분리로 관심사가 명확하게 분리되었습니다.


75-87: attend() 엔드포인트 업데이트 적절함

새로운 GroupV2AttendanceService로 위임하고, 응답 타입이 AttendanceGroupV2Response로 변경되었습니다. 기존 패턴과 일관성을 유지합니다.

src/test/http/group/v2/V2-group-left.http (3)

164-170: 중복 left() 테스트 케이스 좋음

LEFT 상태에서 다시 left() 호출 시 예외 처리를 검증하는 테스트입니다. 주석에 예상 동작이 잘 설명되어 있습니다.


172-177: HOST left() 불가 테스트 케이스 좋음

HOST가 자신의 모임을 떠날 수 없다는 비즈니스 규칙을 검증합니다.


179-185: 상세 조회 테스트로 상태 확인 가능

left() 이후 모임 상세 조회를 통해 참여 인원 및 상태 변경을 확인할 수 있어 유용합니다.

Comment on lines +40 to +41
@NotNull(message = "모임: 참여 방식(joinPolicy)은 필수 입니다.")
GroupV2JoinPolicy joinPolicy,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine CreateGroupV2Request.java file
find . -type f -name "CreateGroupV2Request.java" | head -5

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 166


🏁 Script executed:

# Read the CreateGroupV2Request.java file
cat -n ./src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupV2Request.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1869


🏁 Script executed:

# Search for all usages of CreateGroupV2Request
rg -nP --type=java 'CreateGroupV2Request' -g '!**/test/**' -A 3 -B 3

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 7055


🏁 Script executed:

# Check if there's a V1 version of CreateGroupRequest to see how it handled joinPolicy
find . -type f -name "*CreateGroup*Request.java" | grep -v test | head -20

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 463


🏁 Script executed:

# Examine the V1 CreateGroupRequest to see how joinPolicy was handled
cat -n ./src/main/java/team/wego/wegobackend/group/application/dto/v1/request/CreateGroupRequest.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1580


🏁 Script executed:

# Check the GroupV2JoinPolicy enum to see available values
find . -type f -name "GroupV2JoinPolicy.java" | head -5

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 153


🏁 Script executed:

# Examine the GroupV2JoinPolicy enum
cat -n ./src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2JoinPolicy.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 370


🏁 Script executed:

# Check if there are any tests or documentation that show how joinPolicy should be provided
rg -nP --type=java 'GroupV2JoinPolicy\.' -g '!**/test/**' | head -20

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 562


🏁 Script executed:

# Examine the GroupV2.java to see the full context of joinPolicy handling
sed -n '90,110p' ./src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 799


🏁 Script executed:

# Check how the request is used in the service layer
sed -n '170,200p' ./src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1114


V2 API의 joinPolicy 필드가 필수값(@NotNull)으로 요구되어 클라이언트 업그레이드 필요합니다.

CreateGroupV2Request의 joinPolicy 필드에 @NotNull 제약이 있어 이 필드를 제공하지 않는 요청은 검증 단계에서 실패합니다. V2 API를 사용하는 클라이언트는 요청 시 joinPolicy를 포함해야 합니다.

주목할 점:

  • GroupV2 엔티티에는 joinPolicy가 null일 때 기본값 INSTANT를 설정하는 로직이 있으나(GroupV2.java:99), DTO 검증 단계에서 @NotNull이 먼저 작동합니다.
  • V2 API의 신규 필드이므로, 기존 V1 API 사용자는 업데이트 필요합니다.
  • 기본값 활용이 필요한 경우, 요청 DTO에서 joinPolicy를 선택 필드로 변경하고 @nullable로 처리한 후 서비스 계층의 기본값 로직을 활용할 수 있습니다.
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/application/dto/request/CreateGroupV2Request.java
around lines 40-41, the joinPolicy DTO field is annotated @NotNull which causes
validation failures before the entity-level default (INSTANT) can be applied;
remove the @NotNull constraint and make the field nullable/optional (e.g.,
annotate as @Nullable or use Optional) so clients can omit it, and ensure
service/entity code continues to apply the existing default when joinPolicy is
null.

private final UserRepository userRepository;


// TODO: 참석, 취소 동시성 해결 필요.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "GroupV2AttendanceService.java" 2>/dev/null

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 166


🏁 Script executed:

cat -n ./src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java | head -200

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 9162


상태 변경 후 정원 체크로 인한 레이스 컨디션 발생

attend() 메서드에서 상태 변경(88, 90-92줄)이 먼저 이루어진 후 정원 체크(96-97줄)가 수행됩니다. 두 스레드가 동시에 요청할 때, 첫 번째 스레드가 상태를 ATTEND로 변경한 후 정원을 초과해도 두 번째 스레드는 이미 stale한 카운트로 체크했기 때문에 정원 제한을 무시하고 진행할 수 있습니다.

비관적 락(Hibernate @Lock(LockModeType.PESSIMISTIC_WRITE))이나 Redis 분산 락을 사용하여 상태 변경 전에 정원을 검증하세요.

🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java
around lines 31 and specifically the attend() flow at ~88-97, the code changes
the attendee status before checking capacity which can cause a race where two
threads both pass a stale capacity check; change the flow to perform capacity
validation and update within a single transactional locked section: acquire a DB
pessimistic write lock on the group/attendance rows (or use a Redis distributed
lock keyed by group id) before reading current attendee count, verify capacity,
then set the attendee status and persist, ensuring the operation is atomic and
rolls back on failure; alternatively, move the capacity check before status
mutation only inside the locked transaction so concurrent requests cannot bypass
the limit.

Comment on lines +95 to +100
// 정원 체크(ATTEND만 카운트)
long attendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId,
GroupUserV2Status.ATTEND);
if (attendCount > group.getMaxParticipants()) {
throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

정원 체크 시점에서 레이스 컨디션 발생 가능

reAttend() 또는 save() 이후에 countByGroupIdAndStatus()를 호출하므로, 동시 요청 시 이미 정원이 초과된 상태에서 예외가 발생합니다. 이 경우 트랜잭션 롤백으로 데이터는 정리되지만, 사용자 경험이 좋지 않을 수 있습니다.

또한 Line 98에서 attendCount > group.getMaxParticipants() 조건은 이미 자신이 포함된 카운트이므로 >= 대신 >가 맞습니다. 하지만 정원 체크를 상태 변경 전에 수행하는 것이 더 안전합니다.

🔎 정원 체크를 먼저 수행하는 방식 제안
         if (group.getJoinPolicy() == GroupV2JoinPolicy.INSTANT) {
+            // 정원 체크를 먼저 수행
+            long currentAttendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId,
+                    GroupUserV2Status.ATTEND);
+            if (currentAttendCount >= group.getMaxParticipants()) {
+                throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId);
+            }
+
             if (groupUserV2 != null) {
                 // LEFT, KICKED, REJECTED, CANCELLED -> 재참여
                 groupUserV2.reAttend(); // 내부에서 BANNED만 막고, ATTEND로 변경
             } else {
                 groupUserV2 = GroupUserV2.create(group, userRepository.getReferenceById(userId),
                         GroupUserV2Role.MEMBER);
                 groupUserV2Repository.save(groupUserV2);
             }

-            // 정원 체크(ATTEND만 카운트)
-            long attendCount = groupUserV2Repository.countByGroupIdAndStatus(groupId,
-                    GroupUserV2Status.ATTEND);
-            if (attendCount > group.getMaxParticipants()) {
-                throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId);
-            }
+            long attendCount = currentAttendCount + 1;

             // FULL 자동 전환
🤖 Prompt for AI Agents
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java
around lines 95-100: the capacity check currently runs after you change/save the
user's attendance which can cause race conditions and the comparison logic is
inverted for a pre-check; move the attendCount check to BEFORE changing/saving
the user's status, compute attendCount without the current user and use >=
group.getMaxParticipants() to reject when the group is already full, perform the
check inside the same transaction and obtain a lock on the group row (e.g.,
SELECT FOR UPDATE or repository method with a pessimistic lock) or rely on a DB
constraint to prevent races, and if the check fails throw GroupException
immediately without persisting the attendance change.

Comment on lines +138 to +146
public void requestJoin() {
if (this.status == GroupUserV2Status.BANNED) {
throw new GroupException(GroupErrorCode.GROUP_BANNED_USER);
}
// 이미 ATTEND, PENDING이면 상위에서 걸러도 되고 여기서 방어해도 가능하다고 판단
this.status = GroupUserV2Status.PENDING;
this.joinedAt = LocalDateTime.now();
this.leftAt = null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all calls to requestJoin() method
rg -n "requestJoin\(\)" --type=java -B5 -A5

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 2908


🏁 Script executed:

#!/bin/bash
# Check GroupErrorCode for the suggested error codes
rg -n "ALREADY_ATTEND_GROUP|ALREADY_REQUESTED_TO_JOIN|GROUP_BANNED_USER" --type=java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1795


🏁 Script executed:

#!/bin/bash
# Get the full context around the requestJoin() call in the service
sed -n '100,130p' src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1337


🏁 Script executed:

#!/bin/bash
# Get the full method context from the beginning
sed -n '50,125p' src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 3136


🏁 Script executed:

#!/bin/bash
# Find all requestJoin() calls throughout the codebase
rg "\.requestJoin\(\)" --type=java -B2 -A2

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 806


requestJoin() 메서드에서 ATTEND 및 PENDING 상태 체크 추가 권장

현재 구현은 BANNED 상태만 체크하고 있습니다. 다만 서비스 레이어(GroupV2AttendanceService)에서 호출 전에 ATTEND 및 PENDING 상태를 사전 검증하고 있어 실제 위험은 낮습니다. 그러나 도메인 엔티티 레벨에서의 방어적 처리를 위해 다음과 같은 체크 추가를 권장합니다:

 public void requestJoin() {
     if (this.status == GroupUserV2Status.BANNED) {
         throw new GroupException(GroupErrorCode.GROUP_BANNED_USER);
     }
+    if (this.status == GroupUserV2Status.ATTEND) {
+        throw new GroupException(
+            GroupErrorCode.ALREADY_ATTEND_GROUP,
+            this.group.getId(),
+            this.user.getId()
+        );
+    }
+    if (this.status == GroupUserV2Status.PENDING) {
+        throw new GroupException(
+            GroupErrorCode.ALREADY_REQUESTED_TO_JOIN,
+            this.group.getId(),
+            this.user.getId()
+        );
+    }
     this.status = GroupUserV2Status.PENDING;
     this.joinedAt = LocalDateTime.now();
     this.leftAt = null;
 }

이렇게 하면 엔티티 메서드가 독립적으로 도메인 불변성을 보장하고 다른 방어 메서드들(예: leaveOrCancel())과 일관성을 유지합니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2.java
around lines 138 to 146, the requestJoin() method currently only guards against
BANNED; add defensive checks for ATTEND and PENDING before mutating state.
Specifically, if this.status == GroupUserV2Status.ATTEND throw new
GroupException(GroupErrorCode.GROUP_ALREADY_JOINED) (or the existing error code
used for already-attending users), and if this.status ==
GroupUserV2Status.PENDING throw new
GroupException(GroupErrorCode.GROUP_ALREADY_REQUESTED) (or the appropriate
existing error code for duplicate requests); only then set status = PENDING,
joinedAt = LocalDateTime.now(), leftAt = null. Ensure you use the same
GroupErrorCode enums used elsewhere for consistency.

Comment on lines +3 to +7
public enum GroupV2JoinPolicy {
INSTANT, // 참여 버튼 누르면 즉시 ATTEND
APPROVAL_REQUIRED, // 참여 버튼 누르면 신청 상태로 들어가고 HOST 승인 후 ATTEND
INVITE_ONLY // (미정)초대 받은 사람만 참여
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

INVITE_ONLY 정책이 "미정" 상태로 표시되어 있습니다.

Line 6의 주석에서 INVITE_ONLY가 아직 결정되지 않은 기능("미정")으로 표시되어 있습니다. 프로덕션 코드에 미정 상태의 enum 값을 포함하면 다음과 같은 문제가 발생할 수 있습니다:

  • API 사용자가 해당 정책을 선택했을 때 예상치 못한 동작 발생 가능
  • 향후 해당 정책의 의미가 변경될 경우 breaking change 발생

다음 중 하나를 고려해 주세요:

  1. INVITE_ONLY 정책이 구현될 때까지 enum에서 제거
  2. 선택 시 명확한 에러 메시지와 함께 예외 처리 추가
  3. 구현이 완료되어 있다면 "(미정)" 주석 제거

해당 정책의 구현 상태를 확인하고 싶으시면, 다음 스크립트로 INVITE_ONLY 사용처를 검색할 수 있습니다:

#!/bin/bash
# INVITE_ONLY 정책이 실제로 처리되는 로직이 있는지 확인
rg -nP --type=java -C3 'INVITE_ONLY' -g '!**/test/**'
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupV2JoinPolicy.java
around lines 3–7, the INVITE_ONLY enum value is marked as "(미정)" which is unsafe
to keep in production; either remove INVITE_ONLY until its behavior is
implemented, or if it must remain, add handling so any selection of INVITE_ONLY
throws a clear, documented IllegalStateException (or similar) at runtime with a
user-facing error message, or if the feature is already implemented, simply
remove the "(미정)" comment; verify usages with the provided ripgrep command and
update tests and API docs accordingly.

Comment on lines +74 to +81
{
"sortOrder": 1,
"imageKey": "{{img0_key}}"
},
{
"sortOrder": 2,
"imageKey": "{{img1_key}}"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

sortOrder 시작 인덱스 불일치.

이 페이로드의 sortOrder가 1, 2로 시작하는 반면, 동일 파일의 다른 테스트 케이스(Line 142-152, 294-304)는 0부터 시작합니다. 일관성을 위해 0부터 시작하도록 수정하는 것을 권장합니다.

🔎 수정 제안
   "images": [
     {
-      "sortOrder": 1,
+      "sortOrder": 0,
       "imageKey": "{{img0_key}}"
     },
     {
-      "sortOrder": 2,
+      "sortOrder": 1,
       "imageKey": "{{img1_key}}"
     }
   ]
📝 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.

Suggested change
{
"sortOrder": 1,
"imageKey": "{{img0_key}}"
},
{
"sortOrder": 2,
"imageKey": "{{img1_key}}"
}
{
"sortOrder": 0,
"imageKey": "{{img0_key}}"
},
{
"sortOrder": 1,
"imageKey": "{{img1_key}}"
}
🤖 Prompt for AI Agents
In src/test/http/group/v2/v2-group-create.http around lines 74-81, the payload
uses sortOrder values 1 and 2 while other cases in this file (lines 142-152 and
294-304) start from 0; update the two objects so sortOrder starts at 0 (change
1→0 and 2→1) to keep ordering consistent across tests.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements an approval-based join policy system for group participation, refactoring the existing instant-join functionality and adding support for host-approval workflows.

Key Changes:

  • Introduces GroupV2JoinPolicy enum with three modes: INSTANT (immediate join), APPROVAL_REQUIRED (pending host approval), and INVITE_ONLY (planned feature)
  • Adds new user membership states: PENDING (awaiting approval), REJECTED (approval denied), and CANCELLED (user cancelled their request)
  • Refactors attendance logic into a dedicated GroupV2AttendanceService separating concerns from the main GroupV2Service

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
GroupV2JoinPolicy.java New enum defining join policy modes (INSTANT, APPROVAL_REQUIRED, INVITE_ONLY)
GroupUserV2Status.java Adds PENDING, REJECTED, and CANCELLED states for approval workflow
GroupV2.java Adds joinPolicy field with INSTANT as default when null
GroupUserV2.java Implements approval workflow methods: createPending(), requestJoin(), cancelRequest(), leaveOrCancel()
GroupV2Status.java Adds canLeaveOrCancel() helper for state validation
GroupV2AttendanceService.java New service extracting attendance logic with support for both INSTANT and APPROVAL_REQUIRED policies
GroupV2Service.java Removes attend() and left() methods (moved to GroupV2AttendanceService)
GroupV2Controller.java Updates to use new GroupV2AttendanceService and renamed AttendanceGroupV2Response
CreateGroupV2Request.java Adds required joinPolicy field with @NotNull validation
AttendanceGroupV2Response.java Renamed from AttendGroupV2Response, adds joinPolicy field
GetGroupV2Response.java, CreateGroupV2Response.java, UpdateGroupV2Response.java, GetGroupListV2Response.java, GetMyGroupListV2Response.java All updated to include joinPolicy field in responses
MyMembership.java Adds convenience factory method from single GroupUserV2 instance
GroupErrorCode.java Adds 11 new error codes for approval workflow scenarios
GroupV2QueryRepositoryImpl.java Updates QueryDSL projections to include joinPolicy field
GroupListRow.java, MyGroupListRow.java Add joinPolicy to projection records
v2-group-*.http Updates all test requests to include required joinPolicy field
v2-group-attend-instant.http New test file for INSTANT join policy scenarios
v2-group-attend-approval-required.http New test file for APPROVAL_REQUIRED workflow scenarios
V2-group-left.http Refactored from v2-group-attend.http to focus on leave functionality

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

groupId,
GroupUserV2Status.ATTEND);
if (attendCount > group.getMaxParticipants()) {
// 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The comment on line 130 mentions "reAttend" but in the APPROVAL_REQUIRED flow (lines 112-144), the code never calls reAttend() - it calls requestJoin() instead. The comment should be updated to reflect the actual logic, as it may cause confusion for future maintainers. The comment should say something like "만약 PENDING 상태로 늘었는데 초과하면" or simply remove the misleading reference to reAttend.

Suggested change
// 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외
// 방금 인원이 늘었는데 초과하면 롤백시키기 위해 예외

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +132
// 정원 체크 수행. 재참여 포함해서 체크하는 게 안전
long attendCount = groupUserV2Repository.countByGroupIdAndStatus(
groupId,
GroupUserV2Status.ATTEND);
if (attendCount > group.getMaxParticipants()) {
// 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외
throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId);
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

In the APPROVAL_REQUIRED flow, the code checks if attendCount exceeds maxParticipants (lines 129-132), but this check seems unnecessary since users are only in PENDING status, not ATTEND status. The attendCount only counts ATTEND status users (line 127-128), and requestJoin() sets status to PENDING, not ATTEND. This logic would only make sense if there's a separate approval process that changes PENDING to ATTEND, but that's not shown in this service. Consider removing this check or documenting why it's needed in the approval flow.

Suggested change
// 정원 체크 수행. 재참여 포함해서 체크하는 게 안전
long attendCount = groupUserV2Repository.countByGroupIdAndStatus(
groupId,
GroupUserV2Status.ATTEND);
if (attendCount > group.getMaxParticipants()) {
// 방금 reAttend로 늘었는데 초과하면 롤백시키기 위해 예외
throw new GroupException(GroupErrorCode.GROUP_IS_FULL, groupId);
}
long attendCount = groupUserV2Repository.countByGroupIdAndStatus(
groupId,
GroupUserV2Status.ATTEND);

Copilot uses AI. Check for mistakes.
Comment on lines +134 to +139
// FULL 자동 전환
if (attendCount == group.getMaxParticipants()
&& group.getStatus() == GroupV2Status.RECRUITING) {
group.changeStatus(GroupV2Status.FULL);
}

Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

Similarly, the FULL status auto-transition logic (lines 134-138) doesn't make sense in the APPROVAL_REQUIRED flow. Since requestJoin() creates users with PENDING status (not ATTEND), the attendCount won't change, so the group should never transition to FULL based on a pending request. This logic should likely only exist in the INSTANT join policy flow. Consider removing this block from the APPROVAL_REQUIRED branch or clarifying the intended behavior.

Suggested change
// FULL 자동 전환
if (attendCount == group.getMaxParticipants()
&& group.getStatus() == GroupV2Status.RECRUITING) {
group.changeStatus(GroupV2Status.FULL);
}

Copilot uses AI. Check for mistakes.
group.description = description;
group.maxParticipants = maxParticipants;
group.host = host;
group.joinPolicy = (joinPolicy == null) ? GroupV2JoinPolicy.INSTANT : joinPolicy;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The GroupV2.create() method provides a default value of INSTANT when joinPolicy is null (line 99), but the CreateGroupV2Request has a @NotNull validation on the joinPolicy field. This creates an inconsistency: the validation should prevent null values from reaching this method, making the null check unnecessary. Either remove the @NotNull validation to allow the default, or remove the default assignment since null should never occur. Having both creates confusion about the intended behavior.

Suggested change
group.joinPolicy = (joinPolicy == null) ? GroupV2JoinPolicy.INSTANT : joinPolicy;
group.joinPolicy = joinPolicy;

Copilot uses AI. Check for mistakes.

return AttendanceGroupV2Response.of(group, attendCount, membership);
}
throw new GroupException(GroupErrorCode.INVALID_JOIN_POLICY, String.valueOf(joinPolicy));
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

The INVITE_ONLY join policy is defined in the enum but is not handled in the attend() method of GroupV2AttendanceService. When a group has INVITE_ONLY policy, the attend() method will fall through to line 145 and throw an INVALID_JOIN_POLICY exception. This is likely the intended behavior for now (based on the "미정" comment), but consider adding a more specific error code like INVITE_ONLY_NOT_IMPLEMENTED or INVITE_ONLY_JOIN_NOT_ALLOWED to make the error clearer to API consumers.

Suggested change
throw new GroupException(GroupErrorCode.INVALID_JOIN_POLICY, String.valueOf(joinPolicy));
if (joinPolicy == GroupV2JoinPolicy.INVITE_ONLY) {
// TODO: 추후 초대 전용 참여 정책 확정 시, 전용 에러 코드로 변경 필요.
throw new GroupException(
GroupErrorCode.INVALID_JOIN_POLICY,
"INVITE_ONLY join policy is not supported for attend()."
);
}
throw new GroupException(
GroupErrorCode.INVALID_JOIN_POLICY,
String.valueOf(joinPolicy)
);

Copilot uses AI. Check for mistakes.

@PostMapping("/{groupId}/left")
public ResponseEntity<ApiResponse<AttendGroupV2Response>> left(
public ResponseEntity<ApiResponse<AttendanceGroupV2Response>> left(
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

This method overrides GroupV2ControllerDocs.left; it is advisable to add an Override annotation.

Copilot uses AI. Check for mistakes.
@@ -71,12 +73,12 @@ public ResponseEntity<ApiResponse<GetGroupV2Response>> getGroup(
}

@PostMapping("/{groupId}/attend")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

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

This method overrides GroupV2ControllerDocs.attend; it is advisable to add an Override annotation.

Suggested change
@PostMapping("/{groupId}/attend")
@PostMapping("/{groupId}/attend")
@Override

Copilot uses AI. Check for mistakes.
@LimdaeIl LimdaeIl merged commit 505c0a0 into main Dec 22, 2025
7 checks passed
@LimdaeIl LimdaeIl deleted the feat/group-v2-attendence branch December 22, 2025 13:54
@github-project-automation github-project-automation bot moved this from In progress to Done in WeGo-Together Backend Dec 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEAT] 모임 승인제 요청에 따른 리팩토링

1 participant