Conversation
|
Caution Review failedThe pull request is closed. Walkthrough사용자의 모임 목록을 카테고리별(CURRENT / MY_POST / PAST)로 커서 기반 조회하는 API가 추가되었고, 관련 컨트롤러·서비스·저장소·도메인(enum) 및 테스트 스크립트가 함께 추가/변경되었습니다. 또한 DTO Changes
sequenceDiagram
participant Client
participant Controller as GroupController
participant Service as GroupService
participant Repo as GroupRepository
participant DB as Database
Client->>Controller: GET /api/v1/groups/me (userId,type,cursor,size)
Controller->>Service: getMyGroups(userId,type,cursor,size)
alt type == "current"
Service->>Repo: findCurrentGroupsByUser(userId,statuses,cursor,now,limit+1)
else type == "myPost"
Service->>Repo: findMyPostGroupsByHost(userId,cursor,limit+1)
else type == "past"
Service->>Repo: findPastGroupsByUser(userId,statuses,cursor,now,limit+1)
end
Repo->>DB: execute query (cursor, status, time filters)
DB-->>Repo: rows
Repo-->>Service: entity list (<= limit+1)
Service->>Service: convert entities -> GetGroupListResponse.items\ntrim extra item -> compute nextCursor
Service-->>Controller: GetGroupListResponse(items,nextCursor)
Controller-->>Client: 200 OK ApiResponse(GetGroupListResponse)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 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)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (9)
src/main/java/team/wego/wegobackend/group/domain/entity/MyGroupType.java (1)
18-28:from()메서드에서 문자열 중복 제거를 권장합니다.
switch문에서 하드코딩된 문자열 리터럴("current","myPost","past")이 enum 상수의value필드와 중복됩니다. 값이 변경될 경우 두 곳을 모두 수정해야 하므로 유지보수에 취약합니다.다음과 같이 리팩토링하면 중복을 제거할 수 있습니다:
public static MyGroupType from(String value) { if (value == null) { throw new IllegalArgumentException("MyGroupType 값은 null일 수 없습니다."); } - return switch (value) { - case "current" -> CURRENT; - case "myPost" -> MY_POST; - case "past" -> PAST; - default -> throw new IllegalArgumentException("지원하지 않는 MyGroupType: " + value); - }; + for (MyGroupType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + throw new IllegalArgumentException("지원하지 않는 MyGroupType: " + value); }src/main/java/team/wego/wegobackend/group/presentation/GroupController.java (2)
127-140:type파라미터에 enum 바인딩을 적용하면 검증이 간소화됩니다.현재
type을String으로 받아 서비스에서MyGroupType.from()으로 변환하고 있습니다. Spring은 enum 타입 자동 바인딩을 지원하므로, 컨트롤러에서 직접 enum으로 받으면 잘못된 값에 대한 400 응답이 자동 처리됩니다.@GetMapping("/me") public ResponseEntity<ApiResponse<GetGroupListResponse>> getMyGroups( @RequestParam Long userId, // TODO: 나중에 인증 정보에서 꺼내기 - @RequestParam String type, + @RequestParam MyGroupType type, @RequestParam(required = false) Long cursor, @RequestParam int size ) { GetGroupListResponse response = - groupService.getMyGroups(userId, type, cursor, size); + groupService.getMyGroups(userId, type, cursor, size);서비스 메서드 시그니처도 함께 변경이 필요합니다:
public GetGroupListResponse getMyGroups(Long userId, MyGroupType type, Long cursor, int size)
132-132:size파라미터에 최솟값 검증 추가를 권장합니다.
size가 0 또는 음수일 경우 서비스 레이어에서Math.max(1, ...)로 방어하고 있지만, 컨트롤러 레벨에서@Min(1)검증을 추가하면 잘못된 요청에 대해 더 명확한 400 응답을 반환할 수 있습니다.+import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Max; ... @GetMapping("/me") public ResponseEntity<ApiResponse<GetGroupListResponse>> getMyGroups( @RequestParam Long userId, @RequestParam String type, @RequestParam(required = false) Long cursor, - @RequestParam int size + @RequestParam @Min(1) @Max(50) int size ) {src/main/java/team/wego/wegobackend/group/application/service/GroupService.java (3)
591-616: 세 개의 collector 메서드에서 중복되는 페이지네이션 로직을 추출할 수 있습니다.
getMyCurrentGroups,getMyPostGroups,getMyPastGroups세 메서드 모두 동일한 next-cursor 계산 로직과 응답 변환 패턴을 사용합니다. 람다나Function을 활용해 공통 로직을 추출하면 유지보수성이 향상됩니다.예시 리팩토링:
private GetGroupListResponse buildPaginatedResponse(List<Group> groups, int size) { Long nextCursor = null; if (groups.size() > size) { Group lastExtra = groups.remove(size); nextCursor = lastExtra.getId(); } List<GroupListItemResponse> items = groups.stream() .map(this::toGroupListItemResponse) .toList(); return GetGroupListResponse.of(items, nextCursor); }각 collector에서는 repository 호출 후 이 헬퍼 메서드를 호출하면 됩니다.
Also applies to: 618-636, 638-663
592-592:LocalDateTime.now()를 메서드 시작 시점에 한 번만 호출하는 것이 좋습니다.현재
getMyCurrentGroups와getMyPastGroups에서 각각LocalDateTime.now()를 호출합니다. 만약 향후 동일 트랜잭션 내에서 여러 타입을 조회하는 경우, 시점 차이로 인한 데이터 불일치가 발생할 수 있습니다. 호출 시점을getMyGroups메서드로 올려 일관성을 확보하는 것을 권장합니다.Also applies to: 639-639
605-609: Extract common pagination logic to reduce duplication.The cursor-based pagination pattern is repeated identically in
getMyCurrentGroups,getMyPostGroups, andgetMyPastGroups. Consolidate this logic into a private helper method:private Long extractNextCursor(List<Group> groups, int size) { if (groups.size() > size) { return groups.remove(size).getId(); } return null; }Then replace each instance of the pagination block with:
Long nextCursor = extractNextCursor(groups, size);The risk of
UnsupportedOperationExceptionis minimal since Spring Data JPA@Querymethods return standard mutable ArrayList instances.src/main/java/team/wego/wegobackend/group/domain/repository/GroupRepository.java (2)
41-59: 쿼리 성능을 위한 인덱스 검토를 권장합니다.
findCurrentGroupsByUser와findPastGroupsByUser쿼리는v1_group_users테이블에서user_id,group_user_status컬럼으로 필터링하고,v1_groups테이블에서end_time,group_id로 필터링합니다. 데이터가 증가하면 다음 복합 인덱스가 도움될 수 있습니다:
v1_group_users(user_id, group_user_status)v1_groups(end_time, group_id)또는 기존 인덱스 활용 여부 확인Also applies to: 64-83
88-101:findMyPostGroupsByHost에서DISTINCT가 불필요할 수 있습니다.이 쿼리는
v1_groups테이블만 조회하며JOIN이 없습니다.group_id가 기본키이므로 중복 행이 발생하지 않아DISTINCT가 불필요합니다. 제거하면 약간의 성능 향상이 있을 수 있습니다.@Query(value = """ - SELECT DISTINCT g.* + SELECT g.* FROM v1_groups g WHERE g.deleted_at IS NULL AND g.host_id = :userId AND (:cursor IS NULL OR g.group_id < :cursor) ORDER BY g.group_id DESC LIMIT :limit """, nativeQuery = true)src/test/http/group/me.http (1)
174-220: 내 모임 목록 조회 테스트의 HOST 시나리오는 완전하지만 MEMBER의 myPost 커버리지가 부족합니다.선택지:
- 다양한 사용자 역할(HOST vs MEMBER)에 따른 리스트 필터링을 검증하려면, MEMBER 1도 자신이 생성하지 않은 그룹들 중에서 모임 목록을 조회하는 시나리오를 추가하는 것이 좋습니다.
- 현재 테스트는 HOST의 current/myPost만 검증하고 MEMBER 1은 current/past만 검증합니다.
예를 들어, MEMBER 1이 myPost 타입을 쿼리했을 때 자신이 생성하지 않은 그룹이 결과에 포함되지 않는지 확인하면 더 견고한 테스트가 될 것입니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/main/java/team/wego/wegobackend/group/application/dto/response/GroupImageItemResponse.java(2 hunks)src/main/java/team/wego/wegobackend/group/application/service/GroupService.java(3 hunks)src/main/java/team/wego/wegobackend/group/domain/entity/MyGroupType.java(1 hunks)src/main/java/team/wego/wegobackend/group/domain/repository/GroupRepository.java(2 hunks)src/main/java/team/wego/wegobackend/group/presentation/GroupController.java(1 hunks)src/test/http/group/me.http(1 hunks)
🔇 Additional comments (4)
src/main/java/team/wego/wegobackend/group/application/dto/response/GroupImageItemResponse.java (1)
5-11: 필드 순서 변경에 따른 호환성을 확인하세요.
sortOrder필드가 record의 첫 번째 위치로 이동했습니다. 이 변경은 위치 기반 생성자 호출이나 특정 직렬화 방식(예: CSV, 일부 JSON 라이브러리 설정)에 영향을 줄 수 있습니다. 기존 API 소비자가 있다면 호환성을 확인해 주세요.src/main/java/team/wego/wegobackend/group/domain/repository/GroupRepository.java (1)
41-59: LGTM!현재 진행 중인 모임 조회 쿼리가 잘 구현되었습니다.
end_time IS NULL OR end_time >= :now조건으로 종료 시간이 없거나 아직 종료되지 않은 모임을 정확히 필터링합니다.src/test/http/group/me.http (2)
76-87: 이미지 필드 순서가 실제 응답 구조와 일치하는지 확인이 필요합니다.테스트 요청에서 sortOrder, imageUrl440x240, imageUrl100x100 순서로 필드를 전송하고 있습니다. 이 필드 순서가 GroupImageItemResponse의 정렬 순서와 일치하는지, 그리고 응답 파싱이 이 순서를 따르는지 확인해야 합니다(특히 AI 요약에서 언급된 "GroupImageItemResponse 레코드의 필드 순서 변경").
153-164: 모임 이미지 수정 요청에서 PATCH 메서드 사용이 올바른지 확인이 필요합니다.PATCH /api/v1/groups/images/{{groupId_png_jpg}}를 사용하여 이미지를 교체하고 있습니다. 이것이 부분 업데이트(일부 이미지 교체) 또는 전체 교체를 의도한 것인지, 그리고 서버 구현이 이를 올바르게 처리하는지 검증해야 합니다.
특히 158-164줄의 요청 본문이 배열 형태([...])이므로, PATCH 의미론과 맞는지 확인이 필요합니다. PUT이 더 적절할 수도 있습니다.
| ### 5-3. 내 모임 목록 조회 - MEMBER 1 기준 (과거 참여 모임: past) | ||
| # 참고: endTime < now 인 모임이 없으면 빈 배열이 나올 수 있음 | ||
| GET http://localhost:8080/api/v1/groups/me?userId={{memberId1}}&type=past&size=20 | ||
| Authorization: Bearer {{accessTokenMember1}} | ||
|
|
||
| > {% | ||
| const pastMemberItems = response.body.data.items; | ||
|
|
||
| if (pastMemberItems && pastMemberItems.length > 0) { | ||
| const last = pastMemberItems[pastMemberItems.length - 1]; | ||
| client.global.set("myGroupsPastCursor_member1", last.id); | ||
| } | ||
| %} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find test data files and understand the structure
find . -type f \( -name "*.sql" -o -name "*.json" -o -name "*.yml" -o -name "*.yaml" \) | head -20Repository: WeGo-Together/WeGo_BackEnd
Length of output: 215
🏁 Script executed:
# Look for the test data directory structure
git ls-files | grep -i test | grep -i data | head -20Repository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
# Check the http test file mentioned in the review
cat -n src/test/http/group/me.http | sed -n '240,270p'Repository: WeGo-Together/WeGo_BackEnd
Length of output: 1142
🏁 Script executed:
# Search for group creation or setup in the repository
rg -i "endtime|2025-12-15" --type sql --type json -A 2 -B 2 | head -50Repository: WeGo-Together/WeGo_BackEnd
Length of output: 218
🏁 Script executed:
# Search for endTime mentions in the codebase
rg "endTime|endtime" -i --type java -A 3 -B 1 | head -80Repository: WeGo-Together/WeGo_BackEnd
Length of output: 7648
🏁 Script executed:
# Look for test data initialization or setup files
find . -path ./node_modules -prune -o -type f \( -name "*setup*" -o -name "*fixture*" -o -name "*seed*" \) -printRepository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
# Search for 2025-12-15 date reference
rg "2025-12-15|2025-12" -A 2 -B 2 | head -50Repository: WeGo-Together/WeGo_BackEnd
Length of output: 2316
🏁 Script executed:
# Look for test data or initialization in main resources
find ./src -type f -name "*.sql" -o -name "*.sql.gz"Repository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
# Check if there's test data in resources
find ./src/test -type f \( -name "*.sql" -o -name "*.csv" -o -name "*.json" \) 2>/dev/nullRepository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
# Search for test initialization in group tests
find ./src/test -type f -name "*.java" | grep -i groupRepository: WeGo-Together/WeGo_BackEnd
Length of output: 139
🏁 Script executed:
# Search for the query/repository that handles the type filtering
rg "type.*past|type.*current|type.*upcoming" -A 5 -B 5 --type javaRepository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
# Look for test setup or fixtures that create test data
rg "@BeforeEach|@SetUp|setUp\(\)|init\(\)" --type java | grep -i testRepository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
# Search for GroupRepository and the me endpoint logic
rg "groups/me|type.*filtering|getMy" --type java -A 10Repository: WeGo-Together/WeGo_BackEnd
Length of output: 7515
🏁 Script executed:
# Look at the test file to see what data is created
cat src/test/java/team/wego/wegobackend/group/GroupCursorDummyTest.javaRepository: WeGo-Together/WeGo_BackEnd
Length of output: 4541
🏁 Script executed:
# Find the GroupRepository and the findPastGroupsByUser method
find ./src -type f -name "*Repository*.java" | grep -i groupRepository: WeGo-Together/WeGo_BackEnd
Length of output: 413
🏁 Script executed:
# Search for findPastGroupsByUser implementation
rg "findPastGroupsByUser" -A 15 --type javaRepository: WeGo-Together/WeGo_BackEnd
Length of output: 3632
🏁 Script executed:
# Read the GroupRepository to see the full query implementation
cat src/main/java/team/wego/wegobackend/group/domain/repository/GroupRepository.javaRepository: WeGo-Together/WeGo_BackEnd
Length of output: 3644
Test data lacks past groups for validating "past" type filtering.
The dummy test data (GroupCursorDummyTest.java) creates all groups with future dates starting from 2025-12-10, making it impossible to verify that the type=past query correctly filters groups where endTime < now. The API's filtering logic (g.end_time < :now) is correct, but the test cannot validate it without at least one group with a past endTime.
Create past-dated test groups or adjust the dummy test to include groups with historical dates to properly test the past meeting filter.
🤖 Prompt for AI Agents
In src/test/http/group/me.http around lines 248 to 260, the test scenario for
"type=past" cannot validate filtering because all dummy groups are created with
future endTimes; update the test data to include at least one group whose
endTime is before now (a past date) so the request GET ...?type=past returns a
non-empty past list. Modify GroupCursorDummyTest (or the fixture used by these
HTTP tests) to create one or more groups with historical endTime values (e.g.,
dates before now) and ensure the test captures and asserts those past items,
updating any cursor extraction logic if IDs change.
There was a problem hiding this comment.
Pull request overview
This PR implements the "My Groups" list retrieval feature, enabling users to query their group memberships across three categories: current participating groups, groups they created (myPost), and past groups they participated in. The implementation uses cursor-based pagination consistent with existing list endpoints.
- Adds a new GET
/meendpoint in GroupController with support for filtering by type (current/myPost/past) - Implements three specialized repository queries for different group retrieval scenarios using native SQL
- Introduces MyGroupType enum for type validation with proper error handling
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/team/wego/wegobackend/group/presentation/GroupController.java |
Adds new /me endpoint for retrieving user's group list with type filtering and cursor pagination |
src/main/java/team/wego/wegobackend/group/domain/repository/GroupRepository.java |
Implements three native SQL queries for current, past, and user-created group retrieval with cursor-based pagination |
src/main/java/team/wego/wegobackend/group/domain/entity/MyGroupType.java |
Introduces enum for validating group list type parameter (CURRENT, MY_POST, PAST) |
src/main/java/team/wego/wegobackend/group/application/service/GroupService.java |
Implements getMyGroups method and three private helper methods for different group retrieval scenarios with pagination logic |
src/main/java/team/wego/wegobackend/group/application/dto/response/GroupImageItemResponse.java |
Reorders record fields to place sortOrder first for better readability |
src/test/http/group/me.http |
Adds comprehensive HTTP test scenarios for the new my groups endpoint covering all type filters and pagination |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @GetMapping("/me") | ||
| public ResponseEntity<ApiResponse<GetGroupListResponse>> getMyGroups( | ||
| @RequestParam Long userId, // TODO: 나중에 인증 정보에서 꺼내기 | ||
| @RequestParam String type, |
There was a problem hiding this comment.
The type parameter should include validation annotations to ensure only valid values are accepted. Consider adding @Pattern(regexp = "^(current|myPost|past)$") to validate at the controller level and provide clearer error messages to API consumers before reaching the service layer.
| @RequestParam Long userId, // TODO: 나중에 인증 정보에서 꺼내기 | ||
| @RequestParam String type, | ||
| @RequestParam(required = false) Long cursor, | ||
| @RequestParam int size |
There was a problem hiding this comment.
The size parameter lacks validation constraints. Following the pattern used in getGroupList, consider adding @Min(1) and @Max(50) annotations to enforce valid pagination sizes at the controller level, providing clearer API contracts.
| import team.wego.wegobackend.group.domain.exception.GroupErrorCode; | ||
| import team.wego.wegobackend.group.domain.exception.GroupException; | ||
|
|
||
| @Getter(AccessLevel.PUBLIC) |
There was a problem hiding this comment.
Using @Getter(AccessLevel.PUBLIC) is redundant since PUBLIC is the default access level for @Getter. Simply use @Getter instead.
| @Getter(AccessLevel.PUBLIC) | |
| @Getter |
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
나의 모임 목록 조회를 구현합니다.
🔍 관련 이슈
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.
Summary by CodeRabbit
새로운 기능
테스트
잡무(Chores)
오류 처리
✏️ Tip: You can customize this high-level summary in your review settings.