Conversation
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
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: 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/jsonAlso 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
📒 Files selected for processing (16)
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.javasrc/main/java/team/wego/wegobackend/group/v2/application/dto/common/JoinRequestItem.javasrc/main/java/team/wego/wegobackend/group/v2/application/dto/request/AttendGroupV2Request.javasrc/main/java/team/wego/wegobackend/group/v2/application/dto/request/UpdateGroupV2Request.javasrc/main/java/team/wego/wegobackend/group/v2/application/dto/response/GroupJoinRequestsResponse.javasrc/main/java/team/wego/wegobackend/group/v2/application/service/GroupJoinRequestService.javasrc/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2AttendanceService.javasrc/main/java/team/wego/wegobackend/group/v2/domain/entity/GroupUserV2.javasrc/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupUserV2QueryRepository.javasrc/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupUserV2QueryRepositoryImpl.javasrc/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/JoinRequestRow.javasrc/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.javasrc/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2ControllerDocs.javasrc/test/http/group/v2/v2-group-attend-approval-required.httpsrc/test/http/group/v2/v2-group-get-me-approval-required.httpsrc/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또는 공백 메시지는 적절하게 처리되어joinRequestMessage를null로 설정합니다.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 처리가 잘 되어 있습니다.
items가null인 경우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; |
There was a problem hiding this comment.
사용하지 않는 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.
| 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.
| @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)); | ||
| } |
There was a problem hiding this comment.
엔드포인트 경로가 문서화된 인터페이스와 일치하지 않습니다.
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).
|
|
||
| ### 8.1 내 모임 조회 (MEMBER_A) - current + myStatuses=CANCELLED | ||
| GET http://localhost:8080/api/v2/groups/me?type=current&size=10&myStatuses=CANCELLED | ||
| Authorization: Bearer {{memberAAccessToken}} | ||
|
|
There was a problem hiding this comment.
중복된 테스트 케이스를 제거하고 테스트 순서를 수정하세요.
다음과 같은 문제가 있습니다:
- 중복 테스트: 라인 118-120의 테스트 8.1이 라인 144-146에서 동일하게 반복됩니다.
- 논리적 흐름 오류: 현재 위치에서
myStatuses=CANCELLED를 조회하지만, 실제 취소 동작(테스트 7, 라인 133-138)이 아직 실행되지 않은 시점입니다. 따라서 이 테스트는 빈 결과를 반환하거나 의도하지 않은 결과를 반환할 수 있습니다. - 테스트 번호 혼란: 테스트 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.
| ### 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}} |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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
/attendanceendpoint to retrieve join requests by status - Extended the
attendendpoint to accept an optional join request message - Added database field
joinRequestMessageto 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") |
There was a problem hiding this comment.
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.
| @GetMapping("/{groupId}/join-requests") | |
| @GetMapping("/{groupId}/attendance") |
| import jakarta.validation.constraints.Size; | ||
|
|
||
| public record AttendGroupV2Request( | ||
| @Size(max = 300, message = "모임: 모임 메시지는 최대 300자 이하입니다.") |
There was a problem hiding this comment.
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.
| @Size(max = 300, message = "모임: 모임 메시지는 최대 300자 이하입니다.") | |
| @Size(max = 300, message = "모임: 참가 신청 메시지는 최대 300자 이하입니다.") |
|
|
||
| NO_PERMISSION_TO_VIEW_JOIN_REQUESTS(HttpStatus.FORBIDDEN, | ||
| "모임: 참여 요청 목록 조회 권한이 없습니다. 모임 ID: %s 회원 ID: %s"), | ||
| JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 모임 메시지는 300자 이하입니다."), |
There was a problem hiding this comment.
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.
| JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 모임 메시지는 300자 이하입니다."), | |
| JOIN_REQUEST_MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "모임: 참가 신청 메시지는 최대 300자 이하입니다."), |
| CustomUserDetails userDetails, | ||
| GroupUserV2Status status | ||
| ) { | ||
| if (userDetails == null || userDetails.getId() == null) { |
There was a problem hiding this comment.
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.
| if (userDetails == null || userDetails.getId() == null) { | |
| if (userDetails.getId() == null) { |
| groupUserV2.status.eq(status), | ||
| groupUserV2.groupRole.ne(GroupUserV2Role.HOST) // HOST 제외 | ||
| ) | ||
| // joinRequest는 최신 신청이 위로 오도록 |
There was a problem hiding this comment.
The comment "joinRequest는 최신 신청이 위로 오도록" could be more specific. Consider clarifying that this orders by joinedAt in descending order, such as "최신 참여 요청이 먼저 오도록 joinedAt 기준 내림차순 정렬".
| // joinRequest는 최신 신청이 위로 오도록 | |
| // 최신 참여 요청이 먼저 오도록 joinedAt 기준 내림차순 정렬 |
| } | ||
|
|
||
| public void applyJoinRequestMessage(String message) { | ||
| // 정책: null/blank 허용 여부 결정 |
There was a problem hiding this comment.
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.
| // 정책: null/blank 허용 여부 결정 | |
| // 정책: null 또는 공백만 있는 요청 메시지는 허용하며, 내부적으로 null 로 저장한다. |
| ### 8.1 내 모임 조회 (MEMBER_A) - current + myStatuses=CANCELLED | ||
| GET http://localhost:8080/api/v2/groups/me?type=current&size=10&myStatuses=CANCELLED | ||
| Authorization: Bearer {{memberAAccessToken}} |
There was a problem hiding this comment.
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.
| // status 기본값 방어 | ||
| GroupUserV2Status targetStatus = (status == null) ? GroupUserV2Status.PENDING : status; |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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 |
|
|
||
| return ResponseEntity.ok(ApiResponse.success(HttpStatus.OK.value(), response)); | ||
| } | ||
|
|
There was a problem hiding this comment.
This method overrides GroupV2ControllerDocs.getJoinRequests; it is advisable to add an Override annotation.
| @Override |
명확성과 일관성을 위해 `/join-requests` 엔드포인트의 이름을 `/attendance`로 변경합니다. 이는 그룹 출석 정보를 조회하는 목적을 더 잘 반영합니다. 승인 필수 가입 정책이 적용된 출석 요청 시 메시지가 저장되고 출석 조회 시 반환되는지 확인하는 HTTP 테스트 케이스를 추가합니다.
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
🔍 관련 이슈
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.
Summary by CodeRabbit
새로운 기능
테스트
유효성 검사
✏️ Tip: You can customize this high-level summary in your review settings.