Conversation
사용자와 연결된 그룹을 검색할 수 있는 기능을 도입하여, 그룹 유형(현재, 내 게시물, 이전) 및 상태별로 필터링할 수 있도록 합니다. 페이지네이션 기능을 포함하고 이미지, 태그, 멤버십 정보 등 관련 그룹 정보를 불러옵니다. 대기 시간을 30초에서 5초로 단축합니다
개요"내 모임" V2 목록 조회 기능을 구현합니다. 새로운 DTO, 서비스 계층, 쿼리 메서드 및 컨트롤러 엔드포인트를 통해 사용자가 현재/과거/내가 작성한 모임을 필터링 및 페이지네이션하여 조회할 수 있도록 합니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant Client
participant Controller as GroupV2Controller
participant Service as GroupMyGetV2Service
participant Repository as GroupV2QueryRepository
participant DB as Database
Client->>Controller: GET /api/v2/groups/me<br/>(type, cursor, size, filters)
Controller->>Service: getMyGroups(userId, cursor, size, type, filter, ...)
Service->>Service: Validate userId & default type/filter
Service->>Service: resolveStatuses(filter, includes, excludes)
alt type == MY_POST
Service->>Repository: fetchMyPostGroupRows(userId, cursor, limit, ...)
else type == CURRENT or PAST
Service->>Repository: fetchMyGroupRows(userId, cursor, limit, ...)
end
Repository->>DB: Query groups with status filters<br/>& cursor pagination
DB-->>Repository: List<MyGroupListRow>
Repository-->>Service: MyGroupListRow list
Service->>Repository: fetchTagNamesByGroupIds(groupIds)
Service->>Repository: fetchMainImageUrlsByGroupIds(groupIds)
DB-->>Service: Tags & Images
Service->>Service: Transform rows to Items<br/>(compute remainingSeats,<br/>joinable, MyMembership)
Service-->>Controller: GetMyGroupListV2Response
Controller-->>Client: ApiResponse<GetMyGroupListV2Response>
예상 코드 리뷰 노력🎯 3 (중간 복잡도) | ⏱️ ~25분 검토 시 특별히 주의가 필요한 부분:
관련된 PR들
시
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/main/java/team/wego/wegobackend/group/v2/application/dto/request/MyGroupTypeV2.java (1)
19-29: 대소문자 구분 매칭에 대해 고려해 보세요.현재
from()메서드는 대소문자를 구분하여 "Current"나 "CURRENT"는 실패합니다. API 사용성을 위해 대소문자를 무시하는 매칭을 고려할 수 있습니다.🔎 대소문자 무시 옵션
public static MyGroupTypeV2 from(String value) { if (value == null) { throw new GroupException(GroupErrorCode.MY_GROUP_TYPE_NOT_NULL); } - return switch (value) { + return switch (value.toLowerCase()) { case "current" -> CURRENT; - case "myPost" -> MY_POST; + case "mypost" -> MY_POST; case "past" -> PAST; default -> throw new GroupException(GroupErrorCode.INVALID_MY_GROUP_TYPE, value); }; }src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupV2QueryRepository.java (1)
37-38: 불필요한 빈 줄을 제거하세요.두 개의 연속된 빈 줄은 일반적인 코딩 컨벤션에 맞지 않습니다.
🔎 제안하는 수정
); - - + Map<Long, List<String>> fetchTagNamesByGroupIds(List<Long> groupIds);src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (1)
129-138: API 복잡도 고려사항엔드포인트에 7개의 쿼리 파라미터가 있어 API 사용이 복잡할 수 있습니다. 대부분이 선택적 파라미터이고 적절한 기본값이 설정되어 있어 문제는 아니지만, API 문서화를 잘 해두시기 바랍니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/CreatedBy.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/dto/request/MyGroupTypeV2.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetMyGroupListV2Response.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/service/GroupMyGetV2Service.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupV2QueryRepository.java(2 hunks)src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupV2QueryRepositoryImpl.java(2 hunks)src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/MyGroupListRow.java(1 hunks)src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java(3 hunks)src/test/http/group/v2/v2-group-get-me.http(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/team/wego/wegobackend/group/v2/application/dto/request/MyGroupTypeV2.java (1)
src/main/java/team/wego/wegobackend/group/domain/exception/GroupException.java (1)
GroupException(6-15)
⏰ 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: Agent
- GitHub Check: CodeQL analysis (java)
🔇 Additional comments (17)
src/main/java/team/wego/wegobackend/group/v2/application/service/GroupV2Service.java (1)
61-61: 쿨다운 시간이 30초에서 5초로 크게 감소했습니다.그룹 생성 쿨다운이 6배 단축되었습니다. 이 변경이 의도적인지 확인해 주세요. 5초는 스팸성 그룹 생성을 방지하기에 충분하지 않을 수 있습니다.
src/main/java/team/wego/wegobackend/group/v2/application/dto/common/CreatedBy.java (1)
16-20: LGTM!
of()팩토리 메서드가 projection 데이터에서CreatedBy를 생성할 수 있도록 유연성을 제공합니다. 기존from(User)메서드와 잘 조화됩니다.src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/projection/MyGroupListRow.java (1)
8-33: LGTM!QueryDSL projection을 위한 잘 구성된 record입니다. 필드 타입이 쿼리 결과와 적절히 매핑되어 있고, 그룹 정보, 호스트 정보, 참가자 수, 사용자 멤버십 정보를 명확하게 분리하고 있습니다.
src/test/http/group/v2/v2-group-get-me.http (2)
1-153: LGTM!새로운
/api/v2/groups/me엔드포인트에 대한 포괄적인 E2E 테스트 컬렉션입니다. 회원가입부터 페이지네이션, 타입별 필터링까지 전체 플로우를 잘 커버하고 있습니다.
34-44: 테스트 이미지 파일이 모두 존재합니다.
../../image/resources/img1.png및../../image/resources/img2.jpg파일이src/test/http/image/resources/디렉토리에 정상적으로 존재하므로, 테스트 실행에 문제가 없습니다.src/main/java/team/wego/wegobackend/group/v2/infrastructure/querydsl/GroupV2QueryRepositoryImpl.java (2)
186-285: LGTM!
fetchMyGroupRows메서드가 잘 구현되어 있습니다. 사용자 멤버십에 대한 inner join과 ATTEND 카운트를 위한 left join 구조가 올바릅니다.groupBy에 모든 select 필드가 포함되어 SQL 의미론을 준수합니다.
287-356:fetchMyPostGroupRows에서 left join 사용이 적절합니다.호스트가
group.host에는 있지만group.users에는 명시적 레코드가 없을 수 있으므로myGu에 대한 left join이 올바른 선택입니다.MyMembership레코드는groupUserId,role,status등의 필드가 null일 수 있도록 설계되어 있으므로(참고:MyMembership.from()메서드의orElse로직), 서비스 계층에서 추가적인 null 처리 없이도 안전하게 작동합니다.src/main/java/team/wego/wegobackend/group/v2/domain/repository/GroupV2QueryRepository.java (1)
20-35: 메서드 시그니처가 잘 설계되었습니다.두 메서드의 파라미터 차이가 논리적입니다:
fetchMyGroupRows: 사용자의 멤버십 상태(myStatuses)로 필터링fetchMyPostGroupRows: 호스트 기준 필터링이므로myStatuses불필요src/main/java/team/wego/wegobackend/group/v2/application/service/GroupMyGetV2Service.java (6)
30-42: 입력 검증 및 기본값 처리가 적절합니다.userId null 체크와 적절한 예외 처리가 구현되어 있습니다.
47-52: 타입별 기본 필터 로직이 명확합니다.
CURRENT와MY_POST는ACTIVE,PAST는ARCHIVED필터를 기본값으로 사용하는 것이 직관적입니다.
58-80: 타입별 조건부 로직이 올바릅니다.
MY_POST는 호스트 기준 조회, 나머지는 멤버십 기준 조회로 분기하는 것이 적절하며,myStatuses기본값을ATTEND로 설정하는 것도 합리적입니다.
100-102: Null 안전성 처리가 우수합니다.
participantCount와maxParticipants의 null 체크와 기본값 처리가 잘 되어 있습니다.
142-181: 상태 필터 해석 로직이 견고합니다.필터 기본값 처리, 충돌 제거, 그리고 빈 조건에 대한 기본 include 설정이 잘 구현되어 있습니다. 특히 lines 174-178의 충돌 제거 로직이 데이터 일관성을 보장합니다.
82-84: Java 버전이 이미 21로 설정되어 있으므로 호환성 문제 없음.프로젝트의 build.gradle에
languageVersion = JavaLanguageVersion.of(21)이 설정되어 있어, getLast() 메서드는 Java 21에서 도입된 기능이지만 현재 프로젝트의 Java 버전과 일치합니다. 추가 조치가 필요하지 않습니다.src/main/java/team/wego/wegobackend/group/v2/application/dto/response/GetMyGroupListV2Response.java (2)
39-68: DTO 팩토리 메서드 설계가 우수합니다.계산된 필드(
remainingSeats,joinable)를 팩토리 메서드에서 처리하는 것이 좋은 설계입니다. 특히:
- Line 57:
Math.max(0, ...)로 음수 방지- Lines 58-61:
RECRUITING상태와 남은 좌석을 기반으로 한joinable로직이 명확함- 모든
GroupV2Status값이 switch 표현식에서 처리됨
9-16: 불변 DTO 구조가 적절합니다.Java record를 사용한 불변 DTO와 정적 팩토리 메서드 패턴이 잘 적용되었습니다.
src/main/java/team/wego/wegobackend/group/v2/presentation/GroupV2Controller.java (1)
125-154: 엔드포인트 구조가 적절합니다.새로운
/me엔드포인트가 기존 패턴을 잘 따르고 있으며, 파라미터 설계가 RESTful 원칙에 부합합니다. Line 140의MyGroupTypeV2.from(type)메서드는 null 값과 유효하지 않은 입력값에 대해 적절한GroupException을 발생시키므로 예외 처리가 올바르게 구현되어 있습니다.
There was a problem hiding this comment.
Pull request overview
This PR implements a "My Groups V2 List" feature that allows users to retrieve their groups with various filtering options. The implementation adds a new /api/v2/groups/me endpoint with support for three group types: current groups, past groups, and groups created by the user (myPost).
Key changes include:
- New
GroupMyGetV2Serviceservice layer for handling "my groups" business logic - Two new QueryDSL repository methods (
fetchMyGroupRowsandfetchMyPostGroupRows) for efficient data retrieval - Support for cursor-based pagination with customizable filtering by group status and user membership status
- Enhanced response DTOs that include user membership information
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
GroupV2Controller.java |
Adds new /me GET endpoint with support for filtering by type, cursor pagination, and various status parameters |
GroupMyGetV2Service.java |
New service class implementing business logic for retrieving user's groups with filtering and pagination |
GroupV2QueryRepositoryImpl.java |
Implements two new QueryDSL methods for fetching user's groups with different join strategies based on query type |
GroupV2QueryRepository.java |
Adds interface declarations for the new repository methods |
MyGroupListRow.java |
New projection record for mapping query results with user membership details |
GetMyGroupListV2Response.java |
Response DTO with nested Item record containing group details and calculated fields |
MyGroupTypeV2.java |
Enum defining three types of "my groups" queries with string-based conversion |
CreatedBy.java |
Adds static factory method of() for creating instances from individual fields |
GroupV2Service.java |
Modifies COOL_DOWN_SECONDS constant from 30 to 5 (appears unrelated to main feature) |
v2-group-get-me.http |
New HTTP test file with 12 test scenarios covering signup, login, group creation, and various "my groups" queries |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| BooleanBuilder where = new BooleanBuilder(); | ||
| where.and(group.deletedAt.isNull()); | ||
| where.and(group.host.id.eq(userId)); // ✅ 내가 만든 모임 |
There was a problem hiding this comment.
The inline comment uses an emoji checkmark (✅) which may not render properly in all environments and IDE configurations. Consider using standard comment text instead for better compatibility and professionalism.
| .leftJoin(group.users, myGu) | ||
| .on(myGu.user.id.eq(userId)) | ||
|
|
||
| // ✅ 참가자 수는 ATTEND만 |
There was a problem hiding this comment.
The inline comment uses an emoji checkmark (✅) which may not render properly in all environments and IDE configurations. Consider using standard comment text instead for better compatibility and professionalism.
|
|
||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Unnecessary blank line at the end of the file. Consider removing this extra line to maintain consistent file formatting.
|
|
||
|
|
There was a problem hiding this comment.
Multiple unnecessary blank lines at the end of the file. Consider keeping only one blank line to maintain consistent file formatting.
| host.id, | ||
| host.nickName, | ||
| host.profileImage, | ||
| host.profileMessage, // ✅ 추가 |
There was a problem hiding this comment.
The inline comment uses an emoji checkmark (✅) which may not render properly in all environments and IDE configurations. Consider using standard comment text instead for better compatibility and professionalism.
| .from(group) | ||
| .join(group.host, host) | ||
|
|
||
| // ✅ 내 membership은 있을 수도/없을 수도 → left join + on(userId) |
There was a problem hiding this comment.
The inline comment uses an emoji checkmark (✅) which may not render properly in all environments and IDE configurations. Consider using standard comment text instead for better compatibility and professionalism.
|
|
||
|
|
There was a problem hiding this comment.
Excessive blank lines between method declarations. Consider removing one of these blank lines to maintain consistent spacing with the rest of the interface.
| @RequestParam(required = false) List<GroupV2Status> includeStatuses, | ||
| @RequestParam(required = false) List<GroupV2Status> excludeStatuses, | ||
|
|
||
| // ✅ 내 상태도 보고 싶으면: ATTEND,LEFT,KICKED,BANNED |
There was a problem hiding this comment.
The inline comment uses an emoji checkmark (✅) which may not render properly in all environments and IDE configurations. Consider using standard comment text instead for better compatibility and professionalism.
| // ✅ 내 상태도 보고 싶으면: ATTEND,LEFT,KICKED,BANNED | |
| // NOTE: 내 상태도 보고 싶으면: ATTEND, LEFT, KICKED, BANNED |
| private ResolvedStatuses resolveStatuses( | ||
| GroupListFilter filter, | ||
| List<GroupV2Status> includeStatuses, | ||
| List<GroupV2Status> excludeStatuses | ||
| ) { | ||
| boolean hasInclude = includeStatuses != null && !includeStatuses.isEmpty(); | ||
| boolean hasExclude = excludeStatuses != null && !excludeStatuses.isEmpty(); | ||
|
|
||
| List<GroupV2Status> includes = filter.defaultIncludeStatuses(); | ||
| List<GroupV2Status> excludes = filter.defaultExcludeStatuses(); | ||
|
|
||
| if (includes == null) { | ||
| includes = List.of(); | ||
| } | ||
| if (excludes == null) { | ||
| excludes = List.of(); | ||
| } | ||
|
|
||
| if (hasInclude) { | ||
| includes = includeStatuses; | ||
| excludes = List.of(); | ||
| } | ||
| if (hasExclude) { | ||
| excludes = excludeStatuses; | ||
| } | ||
|
|
||
| // include/exclude/filter 아무것도 안 왔고 ACTIVE면 기본 노출 세트 | ||
| if (!hasInclude && !hasExclude && filter != GroupListFilter.ALL && includes.isEmpty()) { | ||
| includes = List.of(GroupV2Status.RECRUITING, GroupV2Status.FULL); | ||
| } | ||
|
|
||
| // 충돌 제거 | ||
| if (!includes.isEmpty() && !excludes.isEmpty()) { | ||
| var includeSet = java.util.EnumSet.copyOf(includes); | ||
| excludes.forEach(includeSet::remove); | ||
| includes = List.copyOf(includeSet); | ||
| } | ||
|
|
||
| return new ResolvedStatuses(includes, excludes); |
There was a problem hiding this comment.
The resolveStatuses method contains logic that is nearly identical to the status resolution logic in GroupV2Service (lines 77-116). This code duplication increases maintenance burden and risk of inconsistencies. Consider extracting this common logic into a shared utility class or helper method that both services can use.
| private ResolvedStatuses resolveStatuses( | |
| GroupListFilter filter, | |
| List<GroupV2Status> includeStatuses, | |
| List<GroupV2Status> excludeStatuses | |
| ) { | |
| boolean hasInclude = includeStatuses != null && !includeStatuses.isEmpty(); | |
| boolean hasExclude = excludeStatuses != null && !excludeStatuses.isEmpty(); | |
| List<GroupV2Status> includes = filter.defaultIncludeStatuses(); | |
| List<GroupV2Status> excludes = filter.defaultExcludeStatuses(); | |
| if (includes == null) { | |
| includes = List.of(); | |
| } | |
| if (excludes == null) { | |
| excludes = List.of(); | |
| } | |
| if (hasInclude) { | |
| includes = includeStatuses; | |
| excludes = List.of(); | |
| } | |
| if (hasExclude) { | |
| excludes = excludeStatuses; | |
| } | |
| // include/exclude/filter 아무것도 안 왔고 ACTIVE면 기본 노출 세트 | |
| if (!hasInclude && !hasExclude && filter != GroupListFilter.ALL && includes.isEmpty()) { | |
| includes = List.of(GroupV2Status.RECRUITING, GroupV2Status.FULL); | |
| } | |
| // 충돌 제거 | |
| if (!includes.isEmpty() && !excludes.isEmpty()) { | |
| var includeSet = java.util.EnumSet.copyOf(includes); | |
| excludes.forEach(includeSet::remove); | |
| includes = List.copyOf(includeSet); | |
| } | |
| return new ResolvedStatuses(includes, excludes); | |
| private static final class StatusResolutionHelper { | |
| private StatusResolutionHelper() { | |
| // utility class | |
| } | |
| private static ResolvedStatuses resolveStatuses( | |
| GroupListFilter filter, | |
| List<GroupV2Status> includeStatuses, | |
| List<GroupV2Status> excludeStatuses | |
| ) { | |
| boolean hasInclude = includeStatuses != null && !includeStatuses.isEmpty(); | |
| boolean hasExclude = excludeStatuses != null && !excludeStatuses.isEmpty(); | |
| List<GroupV2Status> includes = filter.defaultIncludeStatuses(); | |
| List<GroupV2Status> excludes = filter.defaultExcludeStatuses(); | |
| if (includes == null) { | |
| includes = List.of(); | |
| } | |
| if (excludes == null) { | |
| excludes = List.of(); | |
| } | |
| if (hasInclude) { | |
| includes = includeStatuses; | |
| excludes = List.of(); | |
| } | |
| if (hasExclude) { | |
| excludes = excludeStatuses; | |
| } | |
| // include/exclude/filter 아무것도 안 왔고 ACTIVE면 기본 노출 세트 | |
| if (!hasInclude && !hasExclude && filter != GroupListFilter.ALL && includes.isEmpty()) { | |
| includes = List.of(GroupV2Status.RECRUITING, GroupV2Status.FULL); | |
| } | |
| // 충돌 제거 | |
| if (!includes.isEmpty() && !excludes.isEmpty()) { | |
| var includeSet = java.util.EnumSet.copyOf(includes); | |
| excludes.forEach(includeSet::remove); | |
| includes = List.copyOf(includeSet); | |
| } | |
| return new ResolvedStatuses(includes, excludes); | |
| } | |
| } | |
| private ResolvedStatuses resolveStatuses( | |
| GroupListFilter filter, | |
| List<GroupV2Status> includeStatuses, | |
| List<GroupV2Status> excludeStatuses | |
| ) { | |
| return StatusResolutionHelper.resolveStatuses(filter, includeStatuses, excludeStatuses); |
| private static final int MAX_PAGE_SIZE = 50; | ||
| private static final int GROUP_LIST_IMAGE_LIMIT = 3; | ||
| private static final int COOL_DOWN_SECONDS = 30; | ||
| private static final int COOL_DOWN_SECONDS = 5; |
There was a problem hiding this comment.
The COOL_DOWN_SECONDS has been changed from 30 to 5 seconds. This appears to be unrelated to the "My Groups V2 List" feature and seems like a temporary change for testing purposes. If this change is intentional for production, it significantly reduces the cooldown period which may lead to abuse. If this is only for testing, it should not be committed to the main branch as it weakens the rate limiting mechanism.
| private static final int COOL_DOWN_SECONDS = 5; | |
| private static final int COOL_DOWN_SECONDS = 30; |
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
내 모임 V2 목록 조회 기능 구현합니다.
🔍 관련 이슈
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선
✏️ Tip: You can customize this high-level summary in your review settings.