Skip to content

[FEAT] 모임 PENDING 목록 조회 구현#174

Merged
LimdaeIl merged 3 commits intomainfrom
feat/group-pending
Dec 29, 2025
Merged

[FEAT] 모임 PENDING 목록 조회 구현#174
LimdaeIl merged 3 commits intomainfrom
feat/group-pending

Conversation

@LimdaeIl
Copy link
Collaborator

@LimdaeIl LimdaeIl commented Dec 29, 2025

📝 Pull Request

📌 PR 종류

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

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

✨ 변경 내용

  • 모임 PENDING 목록 조회 구현합니다. 또한 다양한 상태를 조회할 수도 있습니다.
  • 모임 승인제에서 모임 참여 시 메시지도 추가할 수 있도록 수정합니다.

🔍 관련 이슈

🧪 테스트

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

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

🚨 확인해야 할 사항 (Checklist)

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

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

🙋 기타 참고 사항

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

Summary by CodeRabbit

  • 새로운 기능

    • 모임 가입 신청 시 메시지 입력 가능(최대 300자)
    • 호스트가 대기 중인 가입 요청 목록을 조회하는 신규 API 추가(상태 필터 지원)
    • 참석 요청 제출 시 메시지 포함 처리 및 응답에 메시지 유지
  • 테스트

    • 승인 필요 흐름을 검증하는 통합 HTTP 시나리오 추가(가입·승인·거절·조회 포함)
  • 유효성 검사

    • 제목/설명 및 가입 메시지에 대한 사용자 친화적 검증 메시지 추가

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

@LimdaeIl LimdaeIl self-assigned this Dec 29, 2025
Copilot AI review requested due to automatic review settings December 29, 2025 08:21
@LimdaeIl LimdaeIl added the ✨enhancement New feature or request label Dec 29, 2025
@LimdaeIl LimdaeIl moved this from Backlog to In progress in WeGo-Together Backend Dec 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

모임의 승인 필요(join approval) 흐름을 확장하여 가입 요청 메시지(최대 300자)를 저장·검증하고, 호스트가 상태별(예: PENDING)로 가입 요청 목록을 조회할 수 있는 API·서비스·쿼리·도메인 확장을 추가합니다.

Changes

Cohort / File(s) 요약
예외 정의
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java
가입 요청 목록 조회 권한 부족(NO_PERMISSION_TO_VIEW_JOIN_REQUESTS)과 메시지 길이 초과(JOIN_REQUEST_MESSAGE_TOO_LONG) 에러코드 추가
요청 DTO
src/main/java/team/wego/wegobackend/group/v2/application/dto/request/AttendGroupV2Request.java
참석(가입) 요청에 선택적 메시지 필드 추가 및 @Size(max=300) 검증 메시지 지정
응답 / 항목 DTO
src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GroupJoinRequestsResponse.java,
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/JoinRequestItem.java
호스트가 조회하는 가입 요청 응답 형식과 항목 DTO 추가(카운트, 서버시간 포함)
컨트롤러 변경
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java,
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2ControllerDocs.java
POST /attendance 엔드포인트에 선택적 바디 수용(메시지 전달), GET /api/v2/groups/{groupId}/attendance 엔드포인트 추가(상태 필터)
서비스: 조회
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupJoinRequestService.java
호스트 권한 검증 후 QueryRepository로 상태별 가입 요청 조회·DTO 변환 로직 추가
서비스: 출석(참여) 변경
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java
attend 메서드 시그니처에 메시지 파라미터 추가 및 승인 필요 흐름에서 메시지로 Pending 생성
도메인 엔티티
src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2.java
joinRequestMessage 필드 추가, createPending/ requestJoin 오버로드 및 메시지 검증(applyJoinRequestMessage, 300자 제한)
쿼리·프로젝션
src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2QueryRepository.java,
src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupUserV2QueryRepositoryImpl.java,
src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/JoinRequestRow.java
상태별 가입 요청을 가져오는 쿼리 메서드 추가 및 JoinRequestRow 프로젝션 정의(메시지 포함)
테스트 시나리오(HTTP)
src/test/http/group/v2/v2-group-pending.http,
src/test/http/group/v2/v2-group-approval-required-message.http,
src/test/http/group/v2/v2-group-attend-approval-required.http,
src/test/http/group/v2/v2-group-get-me-approval-required.http
메시지 포함 가입 신청, 호스트의 PENDING 조회, 권한 검증 등 시나리오 추가/확장

Sequence Diagram(s)

sequenceDiagram
    actor Host
    actor Member
    participant Controller as GroupV2Controller
    participant AttendSvc as GroupV2AttendanceService
    participant JoinReqSvc as GroupJoinRequestService
    participant GroupRepo as GroupV2Repository
    participant QueryRepo as GroupUserV2QueryRepository

    rect rgb(235,245,255)
    Member->>Controller: POST /api/v2/groups/{id}/attendance\n{ message? }
    Controller->>AttendSvc: attend(userId, groupId, message)
    AttendSvc->>GroupRepo: findGroupById(groupId)
    alt APPROVAL_REQUIRED & no existing membership
        AttendSvc->>GroupRepo: createPending(group, userRef, message)
        Note right of GroupRepo: applyJoinRequestMessage trims/validates\n(<=300 chars) or throw JOIN_REQUEST_MESSAGE_TOO_LONG
    end
    AttendSvc-->>Controller: AttendanceGroupV2Response
    Controller-->>Member: 200 OK
    end

    rect rgb(235,245,255)
    Host->>Controller: GET /api/v2/groups/{id}/attendance?status=PENDING
    Controller->>JoinReqSvc: getJoinRequests(groupId, userDetails, PENDING)
    JoinReqSvc->>GroupRepo: findById(groupId)
    JoinReqSvc->>JoinReqSvc: validate host permission\n(or throw NO_PERMISSION_TO_VIEW_JOIN_REQUESTS)
    JoinReqSvc->>QueryRepo: fetchJoinRequests(groupId, PENDING)
    QueryRepo-->>JoinReqSvc: List<JoinRequestRow>
    JoinReqSvc-->>Controller: GroupJoinRequestsResponse(items...)
    Controller-->>Host: 200 OK + items
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 문을 두드리며 온 편지 한 줄,
말갛게 다듬어 모임에 놓였네.
호스트는 목록을 살피고 결정을 내리며,
작은 메시지로 모두의 발걸음 이어지네.
당근처럼 반짝이는 새 출발! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[FEAT] 모임 PENDING 목록 조회 구현'은 풀 리퀘스트의 주요 변경 사항을 정확하게 요약하며, 모임의 PENDING 상태 목록 조회 기능 구현을 명확하게 설명합니다.
Linked Issues check ✅ Passed PR의 모든 코드 변경사항이 이슈 #173의 요구사항(모임 PENDING 목록 조회 구현, API 추가)을 충족합니다. GroupJoinRequestService, 관련 DTO, 엔티티 메소드, 저장소, 컨트롤러 엔드포인트 및 테스트가 포함되어 있습니다.
Out of Scope Changes check ✅ Passed PR의 모든 변경사항(에러코드, DTO, 엔티티, 저장소, 서비스, 컨트롤러, 테스트)이 PENDING 목록 조회 기능 구현이라는 정의된 범위 내에 있으며, 범위를 벗어난 변경사항이 없습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent 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 722d98f and 9d8be77.

📒 Files selected for processing (2)
  • src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2ControllerDocs.java
  • src/test/http/group/v2/v2-group-approval-required-message.http

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.

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: 4

🧹 Nitpick comments (8)
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java (2)

12-14: 에러 코드 배치를 관련 그룹으로 재구성하는 것을 권장합니다.

새로운 에러 코드들이 기능적으로는 올바르지만, 파일의 기존 그룹화 패턴과 일치하지 않게 최상단에 배치되어 있습니다.

  • NO_PERMISSION_TO_VIEW_JOIN_REQUESTS는 다른 조회 권한 관련 에러들(40-46번 라인) 또는 참여 승인/거절 관련 에러들(78-100번 라인) 근처로 이동하는 것이 일관성 측면에서 더 좋습니다.
  • JOIN_REQUEST_MESSAGE_TOO_LONG은 다른 유효성 검증 에러들(예: 124-132번 라인의 기본 정보 검증) 근처로 이동하는 것을 고려해보세요.

이렇게 관련 에러들을 함께 그룹화하면 향후 유지보수 시 관련 에러 코드를 더 쉽게 찾을 수 있습니다.


14-14: 메시지 용어를 더 명확하게 표현하는 것을 고려하세요.

"모임 메시지"라는 용어보다는 "참여 요청 메시지" 또는 "가입 요청 메시지"로 표현하면 컨텍스트가 더 명확해집니다.

🔎 제안하는 수정
-    JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 모임 메시지는 300자 이하입니다."),
+    JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 참여 요청 메시지는 300자 이하입니다."),
src/test/http/group/v2/v2-group-pending.http (2)

106-113: 메시지 검증 엣지 케이스 테스트 추가를 권장합니다.

AI 요약에 따르면 가입 메시지는 최대 300자로 제한됩니다. 다음 시나리오를 테스트하여 검증 로직을 확인하세요:

  • 300자 정확히 입력한 메시지 (경계값 테스트)
  • 300자를 초과하는 메시지 (에러 코드 JOIN_REQUEST_MESSAGE_TOO_LONG 예상)
  • 빈 문자열 또는 null 메시지 (선택적 필드라면 허용되어야 함)
🔎 엣지 케이스 테스트 예시
### 3-1. 승인제 - 메시지 최대 길이 테스트 (300자)
POST http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attend
Content-Type: application/json
Authorization: Bearer {{anotherMemberAccessToken}}

{
  "message": "a".repeat(300)
}

### 3-2. 승인제 - 메시지 길이 초과 테스트 (301자) -> 400 에러 예상
POST http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attend
Content-Type: application/json
Authorization: Bearer {{anotherMemberAccessToken}}

{
  "message": "a".repeat(301)
}

> {%
  client.test("Should return 400 for message too long", function() {
    client.assert(response.status === 400, "Expected 400 status");
  });
%}

2-2: 환경 변수 사용을 고려해 보세요.

베이스 URL이 http://localhost:8080으로 하드코딩되어 있습니다. 다양한 환경(개발, 스테이징, 프로덕션)에서 테스트를 실행하려면 환경 변수를 사용하는 것이 좋습니다.

🔎 환경 변수 사용 예시

http-client.env.json 파일 생성:

{
  "dev": {
    "baseUrl": "http://localhost:8080"
  },
  "staging": {
    "baseUrl": "https://staging.example.com"
  }
}

테스트 파일에서 사용:

POST {{baseUrl}}/api/v1/auth/signup
Content-Type: application/json

Also applies to: 17-17, 30-30, 53-53, 78-78, 93-93, 107-107, 116-116, 120-120

src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2QueryRepository.java (1)

14-14: 페이지네이션 누락을 확인해주세요.

가입 신청 목록 조회 시 페이지네이션이 없어, 가입 신청이 많은 모임의 경우 성능 문제가 발생할 수 있습니다. 현재 요구사항에서 페이지네이션이 필요하지 않다면, 향후 추가를 고려해야 합니다.

🔎 페이지네이션을 추가하는 예시
-List<JoinRequestRow> fetchJoinRequests(Long groupId, GroupUserV2Status status);
+Page<JoinRequestRow> fetchJoinRequests(Long groupId, GroupUserV2Status status, Pageable pageable);

또는 간단한 제한을 추가:

-List<JoinRequestRow> fetchJoinRequests(Long groupId, GroupUserV2Status status);
+List<JoinRequestRow> fetchJoinRequests(Long groupId, GroupUserV2Status status, int limit);
src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupUserV2QueryRepositoryImpl.java (1)

73-96: 쿼리 구현이 올바르게 작성되었습니다.

  • JoinRequestRow 프로젝션이 올바르게 구성되었습니다.
  • HOST 역할을 가진 사용자를 제외하는 필터링이 적절합니다.
  • joinedAt 내림차순 정렬로 최신 신청이 먼저 조회됩니다.

대량의 가입 신청이 있는 경우 성능을 위해 향후 .limit() 또는 페이지네이션 추가를 고려하세요:

🔎 제한을 추가하는 예시
                 // joinRequest는 최신 신청이 위로 오도록
                 .orderBy(groupUserV2.joinedAt.desc())
+                .limit(100) // 최대 100개로 제한
                 .fetch();
src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GroupJoinRequestsResponse.java (1)

15-27: LocalDateTime.now() 사용에 대한 개선을 고려해주세요.

팩토리 메서드 내에서 LocalDateTime.now()를 직접 호출하면 테스트 작성이 어렵고, 응답 시간의 일관성을 보장하기 어렵습니다.

🔎 개선 방안

방안 1: 서버 시간을 파라미터로 받기

     public static GroupJoinRequestsResponse of(
             Long groupId,
             GroupUserV2Status status,
-            List<JoinRequestItem> items
+            List<JoinRequestItem> items,
+            LocalDateTime serverTime
     ) {
         return new GroupJoinRequestsResponse(
                 groupId,
                 status,
                 items == null ? 0 : items.size(),
                 items,
-                LocalDateTime.now()
+                serverTime
         );
     }

방안 2: Clock 주입 사용 (더 나은 테스트 가능성)

호출하는 서비스 레이어에서 LocalDateTime.now() 또는 Clock.systemDefaultZone()을 사용하여 시간을 전달하면, 테스트 시 Mock Clock을 사용할 수 있습니다.

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

84-94: @Valid 어노테이션 추가를 권장합니다.

메시지 추출 및 전달 로직은 올바르게 구현되었습니다. 하지만 AttendGroupV2Request@Valid 어노테이션을 추가하면 DTO 레벨의 유효성 검증(예: @Size 제약)이 자동으로 실행되어 일관성이 향상됩니다.

🔎 제안하는 개선
     public ResponseEntity<ApiResponse<AttendanceGroupV2Response>> attend(
             @AuthenticationPrincipal CustomUserDetails userDetails,
             @PathVariable Long groupId,
-            @RequestBody(required = false) AttendGroupV2Request request
+            @RequestBody(required = false) @Valid AttendGroupV2Request request
     ) {
📜 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 a6aa916 and 722d98f.

📒 Files selected for processing (16)
  • src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/common/JoinRequestItem.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/request/AttendGroupV2Request.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/request/UpdateGroupV2Request.java
  • src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GroupJoinRequestsResponse.java
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupJoinRequestService.java
  • src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2.java
  • src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2QueryRepository.java
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupUserV2QueryRepositoryImpl.java
  • src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/JoinRequestRow.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/v2/v2-group-attend-approval-required.http
  • src/test/http/group/v2/v2-group-get-me-approval-required.http
  • src/test/http/group/v2/v2-group-pending.http
⏰ 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). (2)
  • GitHub Check: CodeQL analysis (java)
  • GitHub Check: Agent
🔇 Additional comments (18)
src/test/http/group/v2/v2-group-pending.http (2)

1-121: 테스트 시나리오 구조가 잘 구성되어 있습니다.

모임 가입 승인 프로세스의 핵심 흐름(호스트 생성 → 멤버 신청 → 호스트 조회 → 권한 검증)을 논리적으로 잘 표현했습니다.


38-38: 상대 경로(../../image/resources/)가 올바르고 참조된 이미지 파일들(img1.png, img2.jpg)이 실제로 존재합니다. 이미지 파일 경로는 유효하며, 테스트 파일 위치에서 올바르게 해석됩니다.

Likely an incorrect or invalid review comment.

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

157-162: LGTM! 테스트 케이스가 잘 추가되었습니다.

승인 완료 후 멤버가 모임을 나가는 시나리오를 테스트하는 케이스가 적절하게 추가되었습니다. 기존 테스트 패턴과 일관성 있게 작성되었습니다.

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

142-146: 메시지가 승인제 정책에서만 사용됨을 확인했습니다.

APPROVAL_REQUIRED 정책에서 새로운 가입 신청 생성 시에만 메시지가 전달되어, 정책에 맞게 구현되었습니다.


52-52: 메서드 시그니처 변경이 올바르게 적용되었습니다.

message 파라미터가 추가되어 승인제 모임의 가입 신청 시 메시지를 전달할 수 있게 되었습니다. GroupUserV2.createPending 메서드는 applyJoinRequestMessage를 통해 도메인 레벨에서 메시지를 처리하며, 300자 제한을 초과하면 예외를 발생시킵니다. null 또는 공백 메시지는 적절하게 처리되어 joinRequestMessagenull로 설정합니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/request/AttendGroupV2Request.java (1)

1-10: LGTM! 요청 DTO가 적절하게 구현되었습니다.

  • message 필드에 대한 @Size(max=300) 검증이 명확한 한글 메시지와 함께 잘 설정되었습니다.
  • 메시지가 선택사항으로 설계되어, 메시지 없이도 가입 신청이 가능합니다.
  • 최대 길이 300자는 다른 설명 필드들과 일관성이 있습니다.
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/JoinRequestItem.java (1)

1-18: LGTM! DTO가 명확하게 정의되었습니다.

가입 신청 정보를 담는 DTO가 필요한 모든 필드를 포함하여 잘 구성되었습니다. Record 타입 사용으로 불변성이 보장됩니다.

src/main/java/team/wego/wegobackend/group/v2/application/dto/request/UpdateGroupV2Request.java (1)

15-18: LGTM! 검증 메시지가 적절하게 추가되었습니다.

기존 @Size 제약에 명확한 한글 오류 메시지를 추가하여 사용자 경험이 개선되었습니다. 다른 DTO의 검증 메시지와 일관성이 있습니다.

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

23-23: null 처리가 잘 되어 있습니다.

itemsnull인 경우 count를 0으로 설정하는 방어적 코드가 잘 구현되었습니다.

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

6-14: 깔끔한 projection 레코드 구현입니다.

가입 요청 조회를 위한 projection 타입이 명확하게 정의되어 있으며, 필요한 모든 필드가 적절하게 포함되어 있습니다.

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

61-62: 적절한 필드 추가입니다.

300자 제한과 함께 nullable 컬럼으로 정의되어 있어 가입 요청 메시지 저장에 적합합니다.


150-155: 메시지를 포함한 팩토리 메서드가 잘 구현되었습니다.

applyJoinRequestMessage를 호출하여 메시지 유효성 검증을 일관되게 처리하고 있습니다.


167-175: 상태 전환과 메시지 적용이 올바르게 구현되었습니다.

BANNED 체크 후 상태를 PENDING으로 변경하고 메시지를 적용하는 흐름이 명확합니다.


251-263: 견고한 유효성 검증 로직입니다.

null/blank 처리, trim, 길이 제한 검증이 모두 적절하게 구현되어 있습니다. 300자 초과 시 명확한 예외를 발생시킵니다.

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

28-71: 견고한 비즈니스 로직 구현입니다.

  • 사용자 인증 검증이 적절합니다.
  • HOST 권한 확인으로 접근 제어가 올바르게 구현되었습니다.
  • status가 null일 때 PENDING으로 기본값 설정하는 방어 로직이 좋습니다.
  • QueryDSL 결과를 DTO로 매핑하는 흐름이 명확합니다.
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2ControllerDocs.java (2)

62-66: API 문서가 잘 업데이트되었습니다.

선택적 요청 본문 파라미터가 명확하게 문서화되어 있습니다.


270-284: 가입 요청 조회 API가 명확하게 문서화되었습니다.

HOST 권한 제한, status 파라미터, 응답 필드가 상세하게 설명되어 있습니다.

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

55-55: 서비스 의존성이 올바르게 주입되었습니다.

GroupJoinRequestService가 적절하게 주입되어 가입 요청 조회 기능에 사용되고 있습니다.

import team.wego.wegobackend.group.v2.domain.entity.GroupUserV2Status;
import team.wego.wegobackend.group.v2.domain.entity.GroupV2;
import team.wego.wegobackend.group.v2.domain.repository.GroupUserV2QueryRepository;
import team.wego.wegobackend.group.v2.domain.repository.GroupUserV2Repository;
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

사용하지 않는 import를 제거하세요.

GroupUserV2Repository가 import되어 있지만 실제로 사용되지 않습니다. GroupUserV2QueryRepository만 사용됩니다.

🔎 제안하는 수정
-import team.wego.wegobackend.group.v2.domain.repository.GroupUserV2Repository;
📝 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
import team.wego.wegobackend.group.v2.domain.repository.GroupUserV2Repository;
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupJoinRequestService.java
around line 16, the import for
team.wego.wegobackend.group.v2.domain.repository.GroupUserV2Repository is
unused; remove that import line so only the used GroupUserV2QueryRepository
remains imported, then recompile or run static analysis to ensure no other
unused imports exist.

Comment on lines +272 to +282
@GetMapping("/{groupId}/attendance")
public ResponseEntity<ApiResponse<GroupJoinRequestsResponse>> getJoinRequests(
@AuthenticationPrincipal CustomUserDetails userDetails,
@PathVariable Long groupId,
@RequestParam(defaultValue = "PENDING") GroupUserV2Status status
) {
GroupJoinRequestsResponse response =
groupJoinRequestService.getJoinRequests(groupId, userDetails, status);

return ResponseEntity.ok(ApiResponse.success(HttpStatus.OK.value(), response));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

엔드포인트 경로가 문서화된 인터페이스와 일치하지 않습니다.

GroupV2ControllerDocs 인터페이스(line 279)는 @GetMapping("/{groupId}/join-requests")로 정의되어 있지만, 이 구현체는 @GetMapping("/{groupId}/attendance")를 사용하고 있습니다. 이로 인해 다음 문제가 발생합니다:

  • 인터페이스와 구현체의 불일치로 예상치 못한 라우팅 동작 발생 가능
  • /attendance 경로는 가입 요청 조회라는 특정 기능에 비해 너무 포괄적
  • API 문서와 실제 동작이 다를 수 있음
🔎 제안하는 수정
-    @GetMapping("/{groupId}/attendance")
+    @GetMapping("/{groupId}/join-requests")
     public ResponseEntity<ApiResponse<GroupJoinRequestsResponse>> getJoinRequests(
             @AuthenticationPrincipal CustomUserDetails userDetails,
             @PathVariable Long groupId,
             @RequestParam(defaultValue = "PENDING") GroupUserV2Status status
     ) {
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java
around lines 272 to 282, the controller mapping uses
@GetMapping("/{groupId}/attendance") which does not match the documented
interface GroupV2ControllerDocs (line ~279) that declares
@GetMapping("/{groupId}/join-requests"); change the controller mapping to
@GetMapping("/{groupId}/join-requests") so the implementation and docs/interface
align, and verify any related routing tests, API docs, and consumers to ensure
they reference the updated /join-requests path (or alternatively update the
interface/docs to /attendance if that path is preferred across the codebase).

Comment on lines +117 to +121

### 8.1 내 모임 조회 (MEMBER_A) - current + myStatuses=CANCELLED
GET http://localhost:8080/api/v2/groups/me?type=current&size=10&myStatuses=CANCELLED
Authorization: Bearer {{memberAAccessToken}}

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

중복된 테스트 케이스를 제거하고 테스트 순서를 수정하세요.

다음과 같은 문제가 있습니다:

  1. 중복 테스트: 라인 118-120의 테스트 8.1이 라인 144-146에서 동일하게 반복됩니다.
  2. 논리적 흐름 오류: 현재 위치에서 myStatuses=CANCELLED를 조회하지만, 실제 취소 동작(테스트 7, 라인 133-138)이 아직 실행되지 않은 시점입니다. 따라서 이 테스트는 빈 결과를 반환하거나 의도하지 않은 결과를 반환할 수 있습니다.
  3. 테스트 번호 혼란: 테스트 8.1이 테스트 6, 7, 8보다 먼저 나타나 시퀀스가 혼란스럽습니다.

라인 118-121을 제거하고, 라인 144-146의 테스트 8.1만 유지하는 것이 올바른 테스트 흐름입니다.

🔎 수정 제안
-
-### 8.1 내 모임 조회 (MEMBER_A) - current + myStatuses=CANCELLED
-GET http://localhost:8080/api/v2/groups/me?type=current&size=10&myStatuses=CANCELLED
-Authorization: Bearer {{memberAAccessToken}}
-

라인 122-127의 응답 핸들러는 테스트 5 (라인 113-115) 직후로 이동하거나, 필요에 따라 테스트 8 또는 8.1 이후로 배치를 재검토하세요.

🤖 Prompt for AI Agents
In src/test/http/group/v2/v2-group-get-me-approval-required.http around lines
117-121, remove the duplicate test block at lines 118-120 (the 8.1 GET with
myStatuses=CANCELLED) and keep only the identical test at lines 144-146 so the
CANCELLED query runs after the cancellation action; then move or reassign the
response handler currently at lines 122-127 to immediately follow test 5 (lines
~113-115) or alternatively place it after test 8/8.1 as appropriate to preserve
logical sequence and avoid confusing test numbering.

Comment on lines +1 to +121
### 0. 회원가입(HOST - PENDING TEST)
POST http://localhost:8080/api/v1/auth/signup
Content-Type: application/json

{
"email": "host_pending@example.com",
"password": "Test1234!@#",
"nickName": "HostPending",
"phoneNumber": "010-0000-0202"
}

> {%
client.global.set("hostPendingUserId", response.body.data.userId);
%}

### 0-1. 로그인(HOST) - accessToken 발급
POST http://localhost:8080/api/v1/auth/login
Content-Type: application/json

{
"email": "host_pending@example.com",
"password": "Test1234!@#"
}

> {%
client.global.set("hostPendingAccessToken", response.body.data.accessToken);
%}

### 1-1. 모임 V2 이미지 선 업로드 (png / jpg 2장) - imageKey 저장
POST http://localhost:8080/api/v2/groups/images/upload
Content-Type: multipart/form-data; boundary=boundary
Authorization: Bearer {{hostPendingAccessToken}}

--boundary
Content-Disposition: form-data; name="images"; filename="img1.png"
Content-Type: image/png

< ../../image/resources/img1.png
--boundary
Content-Disposition: form-data; name="images"; filename="img2.jpg"
Content-Type: image/jpeg

< ../../image/resources/img2.jpg
--boundary--

> {%
const images = response.body.data.images;
client.global.set("p_img0_key", images[0].imageKey);
client.global.set("p_img1_key", images[1].imageKey);
%}

### 1-2. 모임 V2 생성 (승인제 APPROVAL_REQUIRED)
POST http://localhost:8080/api/v2/groups/create
Content-Type: application/json
Authorization: Bearer {{hostPendingAccessToken}}

{
"title": "V2 승인제 - joinRequestMessage 포함 테스트",
"joinPolicy": "APPROVAL_REQUIRED",
"location": "서울 강남구",
"locationDetail": "역삼역 근처",
"startTime": "2026-12-20T19:00:00",
"endTime": "2026-12-20T21:00:00",
"tags": ["v2", "join-requests", "pending"],
"description": "join-requests 응답에 joinRequestMessage 포함되는지 테스트",
"maxParticipants": 5,
"images": [
{ "sortOrder": 0, "imageKey": "{{p_img0_key}}" },
{ "sortOrder": 1, "imageKey": "{{p_img1_key}}" }
]
}

> {%
client.global.set("groupId_pendingTest", response.body.data.id);
%}

### 2. 회원가입(MEMBER - PENDING TEST)
POST http://localhost:8080/api/v1/auth/signup
Content-Type: application/json

{
"email": "member_pending@example.com",
"password": "Test1234!@#",
"nickName": "MemberPending",
"phoneNumber": "010-1111-0202"
}

> {%
client.global.set("memberPendingId", response.body.data.userId);
%}

### 2-1. 로그인(MEMBER) - accessToken 발급
POST http://localhost:8080/api/v1/auth/login
Content-Type: application/json

{
"email": "member_pending@example.com",
"password": "Test1234!@#"
}

> {%
client.global.set("memberPendingAccessToken", response.body.data.accessToken);
client.global.set("memberPendingUserId", response.body.data.user.userId);
%}

### 3. 승인제 - MEMBER 참여 (PENDING + message 저장)
POST http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attend
Content-Type: application/json
Authorization: Bearer {{memberPendingAccessToken}}

{
"message": "안녕하세요! 참가 신청합니다. joinRequestMessage 테스트용 메시지입니다."
}

### 4. HOST - join-requests 조회 (기본: PENDING)
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
Authorization: Bearer {{hostPendingAccessToken}}

### 5. (옵션) MEMBER가 join-requests 조회 시도 -> 403 기대 (권한 체크)
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
Authorization: Bearer {{memberPendingAccessToken}}
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

테스트 정리(cleanup) 및 응답 검증 추가를 권장합니다.

현재 테스트는 생성된 데이터(사용자, 모임, 이미지)를 정리하지 않으며, 응답 구조를 명시적으로 검증하지 않습니다.

다음을 고려해 주세요:

  • 테스트 종료 시 생성된 리소스를 삭제하는 cleanup 단계 추가
  • 주요 응답에 대한 assertion 추가 (예: 115-117라인의 join-requests 응답이 GroupJoinRequestsResponse 구조와 joinRequestMessage 필드를 포함하는지 확인)
🔎 응답 검증 예시

115-117라인 뒤에 응답 검증 추가:

### 4. HOST - join-requests 조회 (기본: PENDING)
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
Authorization: Bearer {{hostPendingAccessToken}}

> {%
  client.test("Response has joinRequests array", function() {
    client.assert(response.body.data.joinRequests !== undefined, "joinRequests should exist");
  });
  client.test("JoinRequest contains message", function() {
    const requests = response.body.data.joinRequests;
    client.assert(requests.length > 0, "Should have at least one request");
    client.assert(requests[0].message === "안녕하세요! 참가 신청합니다. joinRequestMessage 테스트용 메시지입니다.", "Message should match");
  });
%}
🤖 Prompt for AI Agents
In src/test/http/group/v2/v2-group-pending.http lines 1-121: the test lacks
response assertions and cleanup of created resources; add explicit response
validations after the GET /attendance (line ~115-117) to assert
response.body.data.joinRequests exists, has length > 0, and that the first
request.message equals the submitted join message, and add similar basic
assertions after signup/login/image upload/group create to verify expected
IDs/keys are returned; finally add a cleanup section at the end that deletes the
created users (hostPendingUserId, memberPendingId), the created group
(groupId_pendingTest), and uploaded images (p_img0_key, p_img1_key) using the
appropriate delete endpoints and the saved access tokens so tests do not leave
persistent data.

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 PR implements functionality to retrieve a list of group join requests in PENDING status, with support for filtering by various statuses. The feature allows group hosts to view and manage join requests for approval-required groups.

  • Added new /attendance endpoint to retrieve join requests by status
  • Extended the attend endpoint to accept an optional join request message
  • Added database field joinRequestMessage to store user-provided messages when joining

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
GroupV2Controller.java Adds new GET endpoint /attendance for retrieving join requests and updates attend endpoint to accept optional message parameter
GroupV2ControllerDocs.java Adds Swagger documentation for the join requests endpoint (contains path mismatch bug)
GroupJoinRequestService.java New service class implementing business logic for retrieving join requests with host permission validation
GroupV2AttendanceService.java Updates attend method to accept and process join request messages
GroupUserV2.java Adds joinRequestMessage field and methods to handle message validation and storage with 300-character limit
GroupUserV2QueryRepository.java Adds interface method for fetching join requests by status
GroupUserV2QueryRepositoryImpl.java Implements QueryDSL query to fetch join requests excluding hosts, ordered by most recent
JoinRequestRow.java New projection record for QueryDSL join request query results
GroupJoinRequestsResponse.java New response DTO containing join request list with metadata
JoinRequestItem.java New DTO representing individual join request items with user and status information
AttendGroupV2Request.java New request DTO for attend endpoint with optional message field and size validation
UpdateGroupV2Request.java Adds validation messages to existing fields (unrelated to main PR purpose)
GroupErrorCode.java Adds error codes for permission denial and message length validation
v2-group-pending.http New HTTP test file for testing the pending list retrieval feature
v2-group-get-me-approval-required.http Adds test case for cancelled status filtering (scope unclear)
v2-group-attend-approval-required.http Adds test case for left endpoint (scope unclear)

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

- 응답 items에는 joinRequestMessage(신청 메시지)가 포함됩니다.
"""
)
@GetMapping("/{groupId}/join-requests")
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The API endpoint path uses "/attendance" but the Swagger documentation at line 279 uses "/join-requests". These endpoint paths are inconsistent and will cause the API call to fail. The paths should match - update either the controller mapping or the documentation annotation to use the same path.

Suggested change
@GetMapping("/{groupId}/join-requests")
@GetMapping("/{groupId}/attendance")

Copilot uses AI. Check for mistakes.
import jakarta.validation.constraints.Size;

public record AttendGroupV2Request(
@Size(max = 300, message = "모임: 모임 메시지는 최대 300자 이하입니다.")
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The error message uses "모임 메시지" which is ambiguous. Consider using a more specific term like "참가 신청 메시지" or "참여 요청 메시지" to better clarify that this refers to the join request message, not a general group message.

Suggested change
@Size(max = 300, message = "모임: 모임 메시지는 최대 300자 이하입니다.")
@Size(max = 300, message = "모임: 참가 신청 메시지는 최대 300자 이하입니다.")

Copilot uses AI. Check for mistakes.

NO_PERMISSION_TO_VIEW_JOIN_REQUESTS(HttpStatus.FORBIDDEN,
"모임: 참여 요청 목록 조회 권한이 없습니다. 모임 ID: %s 회원 ID: %s"),
JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 모임 메시지는 300자 이하입니다."),
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The error message "모임: 모임 메시지는 300자 이하입니다" is inconsistent with the validation message in AttendGroupV2Request which says "최대 300자 이하입니다". Additionally, "모임 메시지" is ambiguous - consider using a more specific term like "참가 신청 메시지" or "참여 요청 메시지" for clarity and consistency across both messages.

Suggested change
JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 모임 메시지는 300자 이하입니다."),
JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 참가 신청 메시지는 최대 300자 이하입니다."),

Copilot uses AI. Check for mistakes.
CustomUserDetails userDetails,
GroupUserV2Status status
) {
if (userDetails == null || userDetails.getId() == null) {
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The null check for userDetails is redundant since the @AuthenticationPrincipal annotation in the controller ensures a non-null CustomUserDetails object for authenticated requests. If an unauthenticated request is possible, Spring Security should handle it at the filter level rather than in the service layer.

Suggested change
if (userDetails == null || userDetails.getId() == null) {
if (userDetails.getId() == null) {

Copilot uses AI. Check for mistakes.
groupUserV2.status.eq(status),
groupUserV2.groupRole.ne(GroupUserV2Role.HOST) // HOST 제외
)
// joinRequest는 최신 신청이 위로 오도록
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The comment "joinRequest는 최신 신청이 위로 오도록" could be more specific. Consider clarifying that this orders by joinedAt in descending order, such as "최신 참여 요청이 먼저 오도록 joinedAt 기준 내림차순 정렬".

Suggested change
// joinRequest는 최신 신청이 위로 오도록
// 최신 참여 요청이 먼저 오도록 joinedAt 기준 내림차순 정렬

Copilot uses AI. Check for mistakes.
}

public void applyJoinRequestMessage(String message) {
// 정책: null/blank 허용 여부 결정
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The comment "정책: null/blank 허용 여부 결정" suggests an undecided policy. This comment should be updated to clearly state the decided policy. Based on the code, the policy appears to be "null and blank messages are allowed and will be stored as null", which should be documented clearly.

Suggested change
// 정책: null/blank 허용 여부 결정
// 정책: null 또는 공백만 있는 요청 메시지는 허용하며, 내부적으로 null 로 저장한다.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +120
### 8.1 내 모임 조회 (MEMBER_A) - current + myStatuses=CANCELLED
GET http://localhost:8080/api/v2/groups/me?type=current&size=10&myStatuses=CANCELLED
Authorization: Bearer {{memberAAccessToken}}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

This new test case for querying groups with "myStatuses=CANCELLED" appears unrelated to the PR's stated purpose of implementing PENDING list retrieval. If this is necessary for testing the new functionality, please explain the relationship in the PR description. Otherwise, consider moving this change to a separate PR for better change tracking.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +53
// status 기본값 방어
GroupUserV2Status targetStatus = (status == null) ? GroupUserV2Status.PENDING : status;
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The null check for the status parameter is defensive but unnecessary since the @RequestParam annotation in the controller provides a default value of "PENDING". This check can be removed to simplify the code.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +120
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
Authorization: Bearer {{hostPendingAccessToken}}

### 5. (옵션) MEMBER가 join-requests 조회 시도 -> 403 기대 (권한 체크)
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The HTTP test uses the endpoint path "/attendance?status=PENDING" but the Swagger documentation (@GetMapping annotation) specifies "/join-requests". This inconsistency should be caught by the critical bug comment already filed. However, note that the test will fail until the path mismatch is resolved.

Suggested change
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
Authorization: Bearer {{hostPendingAccessToken}}
### 5. (옵션) MEMBER가 join-requests 조회 시도 -> 403 기대 (권한 체크)
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/attendance?status=PENDING
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/join-requests?status=PENDING
Authorization: Bearer {{hostPendingAccessToken}}
### 5. (옵션) MEMBER가 join-requests 조회 시도 -> 403 기대 (권한 체크)
GET http://localhost:8080/api/v2/groups/{{groupId_pendingTest}}/join-requests?status=PENDING

Copilot uses AI. Check for mistakes.

return ResponseEntity.ok(ApiResponse.success(HttpStatus.OK.value(), response));
}

Copy link

Copilot AI Dec 29, 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.getJoinRequests; it is advisable to add an Override annotation.

Suggested change
@Override

Copilot uses AI. Check for mistakes.
명확성과 일관성을 위해 `/join-requests` 엔드포인트의 이름을 `/attendance`로 변경합니다.
이는 그룹 출석 정보를 조회하는 목적을 더 잘 반영합니다.

승인 필수 가입 정책이 적용된 출석 요청 시 메시지가 저장되고 출석 조회 시 반환되는지 확인하는 HTTP 테스트 케이스를 추가합니다.
@LimdaeIl LimdaeIl merged commit b86b220 into main Dec 29, 2025
1 check was pending
@LimdaeIl LimdaeIl deleted the feat/group-pending branch December 29, 2025 08:34
@github-project-automation github-project-automation bot moved this from In progress to Done in WeGo-Together Backend Dec 29, 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] 모임 PENDING 목록 조회 구현

1 participant