Skip to content

Comments

Feat: [FN-308] 특정 그룹 조회 API 구현#8

Open
stoneTiger0912 wants to merge 7 commits intomainfrom
feat/find-specific-group
Open

Feat: [FN-308] 특정 그룹 조회 API 구현#8
stoneTiger0912 wants to merge 7 commits intomainfrom
feat/find-specific-group

Conversation

@stoneTiger0912
Copy link
Member

@stoneTiger0912 stoneTiger0912 commented Feb 18, 2026

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 그룹 멤버 조회 기능 추가
    • 그룹 검색 및 필터링 기능 확대 (전체 그룹, 내 그룹, 생성한 그룹)
    • 카테고리별 그룹 필터링 및 커서 기반 페이지네이션 지원
  • 개선 사항

    • 그룹 삭제 API 개선

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Walkthrough

이 PR은 커서 기반 페이징을 지원하는 그룹 조회 기능을 추가합니다. 새로운 컨트롤러(GroupQueryController, MemberController), 페이징 요청/응답 DTO, QueryDSL 기반 저장소 구현, 그리고 관련 서비스 및 use case를 도입합니다. 또한 GroupMember 도메인 모델을 리팩토링하여 RoleEntity 객체로 역할을 관리합니다.

Changes

Cohort / File(s) Summary
컨트롤러 - 쿼리 및 조회
src/main/java/flipnote/group/adapter/in/web/GroupQueryController.java, src/main/java/flipnote/group/adapter/in/web/MemberController.java, src/main/java/flipnote/group/adapter/in/web/GroupController.java
그룹 조회(전체/내 소유/생성한), 그룹 멤버 조회 엔드포인트 추가; GroupController에서 불필요한 주석 제거 및 메서드 서명 정렬.
컨트롤러 - 스케폴딩
src/main/java/flipnote/group/adapter/in/web/InvitationController.java
초대 및 멤버십 관련 향후 엔드포인트를 위한 플레이스홀더 TODO 주석만 포함한 스켈레톤 컨트롤러.
DTO 및 API 모델
src/main/java/flipnote/group/api/dto/request/CursorPagingRequest.java, src/main/java/flipnote/group/api/dto/request/GroupListRequestDto.java, src/main/java/flipnote/group/api/dto/response/CursorPagingResponseDto.java, src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java
커서 페이징 요청/응답 모델, 그룹 목록 필터링 요청, 멤버 조회 응답 DTO 추가.
use case 포트 및 명령/결과
src/main/java/flipnote/group/application/port/in/FindGroupUseCase.java, src/main/java/flipnote/group/application/port/in/FindGroupMemberUseCase.java, src/main/java/flipnote/group/application/port/in/command/FindGroupMemberCommand.java, src/main/java/flipnote/group/application/port/in/result/FindGroupMemberResult.java, src/main/java/flipnote/group/application/port/in/result/FindAllGroupResult.java
그룹 및 멤버 조회용 use case 인터페이스, 명령/결과 타입 추가.
저장소 포트 - 아웃바운드
src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java, src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java, src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java
RoleEntity 기반 저장소 메서드 서명 업데이트, 커서 페이징 쿼리 메서드 추가, 멤버 정보 조회 메서드 추가.
서비스 구현
src/main/java/flipnote/group/application/service/FindGroupService.java, src/main/java/flipnote/group/application/service/FindGroupMemberService.java, src/main/java/flipnote/group/application/service/CreateGroupService.java
그룹 조회(전체/내 소유/생성한) 메서드 및 멤버 조회 서비스 추가; RoleEntity 처리로 CreateGroupService 업데이트.
도메인 모델 - 그룹 및 멤버
src/main/java/flipnote/group/domain/model/group/GroupInfo.java, src/main/java/flipnote/group/domain/model/group/GroupId.java, src/main/java/flipnote/group/domain/model/member/GroupMember.java, src/main/java/flipnote/group/domain/model/member/MemberInfo.java, src/main/java/flipnote/group/domain/model/user/UserId.java
GroupInfo 레코드 추가; GroupId/UserId 제거; GroupMember를 RoleEntity 기반으로 리팩토링하고 빌더 패턴 도입; MemberInfo 값 객체 추가.
엔티티 및 매퍼
src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java, src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java
GroupMemberEntity의 groupRoleId를 RoleEntity 관계로 변경; 매퍼에 toDomain/toMemberInfo 메서드 추가.
어댑터 - 저장소 구현
src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java, src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java, src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java
RoleEntity 매개변수 처리, 커서 페이징 메서드 추가, 멤버 정보 조회 구현; GroupRoleRepositoryAdapter는 RoleEntity 반환으로 업데이트.
저장소 인터페이스 - JPA
src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java, src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java, src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java
기존 GroupMemberRepository 제거; GroupMemberRepositoryRepository 신규 추가(역할 기반 쿼리 포함); GroupRepository가 GroupRepositoryCustom 확장.
QueryDSL 저장소
src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryCustom.java, src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java
커서 기반 그룹 조회 메서드 인터페이스 및 QueryDSL 구현 추가.
인프라 설정
src/main/java/flipnote/group/infrastructure/config/QuerydslConfig.java
QueryDSL JPAQueryFactory 빈 설정 추가.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant GroupQueryController
    participant FindGroupService
    participant GroupRepositoryPort
    participant GroupRepositoryImpl
    participant Database

    Client->>GroupQueryController: GET /v1/groups<br/>(userId, GroupListRequestDto)
    activate GroupQueryController
    GroupQueryController->>FindGroupService: findAllGroup(userId, req)
    activate FindGroupService
    FindGroupService->>GroupRepositoryPort: findAllByCursor(cursorId, category, size)
    activate GroupRepositoryPort
    GroupRepositoryPort->>GroupRepositoryImpl: findAllByCursor(...)
    activate GroupRepositoryImpl
    GroupRepositoryImpl->>Database: QueryDSL 쿼리<br/>(그룹 조회, 필터링)
    Database-->>GroupRepositoryImpl: List<GroupInfo>
    deactivate GroupRepositoryImpl
    GroupRepositoryPort-->>FindGroupService: List<GroupInfo>
    deactivate GroupRepositoryPort
    FindGroupService->>FindGroupService: 페이지 계산<br/>(hasNext, nextCursor)
    FindGroupService-->>GroupQueryController: CursorPagingResponseDto<GroupInfo>
    deactivate FindGroupService
    GroupQueryController-->>Client: 200 OK<br/>CursorPagingResponseDto
    deactivate GroupQueryController
Loading
sequenceDiagram
    actor Client
    participant MemberController
    participant FindGroupMemberService
    participant GroupMemberRepositoryPort
    participant GroupMemberRepositoryAdapter
    participant Database

    Client->>MemberController: GET /v1/groups/{groupId}/members<br/>(userId, groupId)
    activate MemberController
    MemberController->>FindGroupMemberService: findGroupMember(cmd)
    activate FindGroupMemberService
    FindGroupMemberService->>GroupMemberRepositoryPort: existsUserInGroup(groupId, userId)
    activate GroupMemberRepositoryPort
    GroupMemberRepositoryPort->>GroupMemberRepositoryAdapter: existsUserInGroup(...)
    activate GroupMemberRepositoryAdapter
    GroupMemberRepositoryAdapter->>Database: 사용자 멤버십 확인
    Database-->>GroupMemberRepositoryAdapter: 확인 완료/예외
    deactivate GroupMemberRepositoryAdapter
    GroupMemberRepositoryPort-->>FindGroupMemberService: void/예외
    deactivate GroupMemberRepositoryPort
    FindGroupMemberService->>GroupMemberRepositoryPort: findMemberInfo(groupId)
    activate GroupMemberRepositoryPort
    GroupMemberRepositoryPort->>GroupMemberRepositoryAdapter: findMemberInfo(...)
    activate GroupMemberRepositoryAdapter
    GroupMemberRepositoryAdapter->>Database: 그룹 멤버 조회
    Database-->>GroupMemberRepositoryAdapter: List<GroupMemberEntity>
    GroupMemberRepositoryAdapter->>GroupMemberRepositoryAdapter: 매퍼: toMemberInfo(entities)
    GroupMemberRepositoryAdapter-->>GroupMemberRepositoryPort: List<MemberInfo>
    deactivate GroupMemberRepositoryAdapter
    GroupMemberRepositoryPort-->>FindGroupMemberService: List<MemberInfo>
    deactivate GroupMemberRepositoryPort
    FindGroupMemberService-->>MemberController: FindGroupMemberResult
    deactivate FindGroupMemberService
    MemberController->>MemberController: 응답 변환<br/>(FindGroupMemberResponseDto)
    MemberController-->>Client: 200 OK<br/>FindGroupMemberResponseDto
    deactivate MemberController
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 커서가 춤을 추고, 페이지를 넘기며,
QueryDSL 마법으로 그룹들이 떠다니네.
RoleEntity 옷을 입은 멤버들,
새로운 조회의 정원에서 피어난다.
토끼가 전한다: 쿼리 최적화의 즐거움! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 특정 그룹 조회 API 구현을 나타내며, PR의 주요 변경 사항인 그룹 목록 조회 기능(findGroup, findMyGroup, findCreatedGroup)과 그룹 멤버 조회 기능을 정확히 반영하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/find-specific-group

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java (1)

33-36: ⚠️ Potential issue | 🟡 Minor

Javadoc @return 태그가 void 메서드에 남아 있습니다.

메서드 반환형이 void로 변경되었으나 Javadoc의 @return 태그가 제거되지 않았습니다.

📝 수정 제안
 /**
  * 유저가 그룹 내에 있는지 체크
  * `@param` groupId
  * `@param` userId
- * `@return`
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`
around lines 33 - 36, In GroupMemberRepositoryAdapter, locate the Javadoc block
that documents `@param` groupId and `@param` userId for the method whose return type
was changed to void and remove the stray `@return` tag; ensure the Javadoc now
only contains the descriptive text and the `@param` tags (and update the
description if necessary) so it correctly matches the void method signature.
🧹 Nitpick comments (16)
src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java (1)

15-16: GroupMemberRepositoryRepository 이름에 "Repository"가 중복됩니다.

GroupMemberRepositoryRepository는 명명 오류로 보입니다. 이 파일뿐 아니라 해당 인터페이스 자체를 GroupMemberJpaRepository 등으로 변경하는 것이 좋겠습니다. (해당 인터페이스 파일 리뷰에서 자세히 언급합니다.)

Also applies to: 26-26

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java`
around lines 15 - 16, The import and type name GroupMemberRepositoryRepository
is duplicated ("Repository" twice); rename the interface to a clearer name such
as GroupMemberJpaRepository, update the import in GroupRoleRepositoryAdapter
(and any other files) to use the new name, and update all usages (field types,
constructor parameters, method calls) that reference
GroupMemberRepositoryRepository inside this class to the new symbol; perform the
refactor via your IDE (rename file and type) or update the interface file name
and all references so imports and types (e.g., the field/parameter where
GroupMemberRepositoryRepository is used) match the new GroupMemberJpaRepository
identifier.
src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java (1)

6-6: 사용되지 않는 import가 있습니다.

org.springframework.data.jpa.repository.Queryflipnote.group.adapter.out.entity.RoleEntity가 import되었지만 이 인터페이스에서 사용되지 않습니다.

🧹 미사용 import 제거
-import org.springframework.data.jpa.repository.Query;
-
-import flipnote.group.adapter.out.entity.GroupMemberEntity;
-import flipnote.group.adapter.out.entity.RoleEntity;
+import flipnote.group.adapter.out.entity.GroupMemberEntity;

Also applies to: 9-9

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`
at line 6, Remove the unused imports in GroupMemberRepositoryRepository: delete
the import org.springframework.data.jpa.repository.Query and the import
flipnote.group.adapter.out.entity.RoleEntity since they are not referenced in
the interface (GroupMemberRepositoryRepository); ensure no other code in that
file depends on Query or RoleEntity and run a quick compile to confirm
unused-import warnings are gone.
src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java (1)

11-11: existsUserInGroupvoid 반환 타입은 포트 계약 수준에서의 의미 불명확성을 야기합니다.

포트를 구현하는 어댑터가 미가입 케이스에서 예외를 던지는지, 아니면 단순히 아무것도 하지 않는지를 시그니처만으로 파악할 수 없습니다. 포트에 Javadoc 또는 명칭 변경(assertUserInGroup)으로 명확한 계약을 명시하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java`
at line 11, The method signature void existsUserInGroup(Long groupId, Long
userId) is ambiguous at the port level; update the contract to make behavior
explicit by either (A) changing the return type to boolean (e.g.,
existsUserInGroup returns true/false) so adapters return existence, or (B)
renaming to assertUserInGroup and document/implement it to throw a specific
unchecked exception when the user is not a member. Add Javadoc to
GroupMemberRepositoryPort describing the chosen contract (return boolean vs
throw) so adapters implementing existsUserInGroup/assertUserInGroup know whether
to return a flag or throw an exception.
src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java (1)

8-14: API 응답 DTO가 도메인 타입 MemberInfo를 직접 노출합니다.

MemberInfo는 도메인 레이어의 객체로, 이를 API 응답 DTO에 그대로 사용하면 도메인 변경 사항이 API 계약에 직접 영향을 미치게 됩니다. MemberInfoDto와 같은 별도의 API 전용 응답 타입으로 분리하는 것을 권장합니다.

♻️ 리팩토링 제안

별도 API 응답 레코드 생성:

// api/dto/response/MemberInfoDto.java (신규)
public record MemberInfoDto(Long userId, GroupMemberRole role) {
    public static MemberInfoDto from(MemberInfo info) {
        return new MemberInfoDto(info.getUserId(), info.getRole());
    }
}

FindGroupMemberResponseDto 수정:

 public record FindGroupMemberResponseDto(
-    List<MemberInfo> memberInfoList
+    List<MemberInfoDto> memberInfoList
 ) {
     public static FindGroupMemberResponseDto from(FindGroupMemberResult result) {
-        return new FindGroupMemberResponseDto(result.memberInfoList());
+        return new FindGroupMemberResponseDto(
+            result.memberInfoList().stream()
+                .map(MemberInfoDto::from)
+                .toList()
+        );
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java`
around lines 8 - 14, The response DTO currently exposes the domain type
MemberInfo; create a new API record MemberInfoDto (e.g., record
MemberInfoDto(Long userId, GroupMemberRole role) with a static from(MemberInfo))
and update FindGroupMemberResponseDto.from to convert result.memberInfoList()
into a List<MemberInfoDto> (mapping each MemberInfo via MemberInfoDto.from) so
FindGroupMemberResponseDto holds List<MemberInfoDto> instead of
List<MemberInfo>, leaving FindGroupMemberResult and other domain classes
untouched.
src/main/java/flipnote/group/application/service/FindGroupMemberService.java (1)

25-35: 읽기 전용 서비스 메서드에 @Transactional(readOnly = true) 어노테이션이 누락되어 있습니다.

findGroupMember는 순수 조회 작업으로, @Transactional(readOnly = true) 어노테이션을 추가하면 JPQL 쿼리 최적화(dirty checking 비활성화 등)의 이점을 얻을 수 있습니다.

♻️ 수정 제안
+ import org.springframework.transaction.annotation.Transactional;
  ...
  `@Override`
+ `@Transactional`(readOnly = true)
  public FindGroupMemberResult findGroupMember(FindGroupMemberCommand cmd) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/application/service/FindGroupMemberService.java`
around lines 25 - 35, The findGroupMember method in FindGroupMemberService is a
read-only operation but lacks the `@Transactional`(readOnly = true) annotation;
update the FindGroupMemberService class by annotating the
findGroupMember(FindGroupMemberCommand cmd) method with `@Transactional`(readOnly
= true) (and add the import for
org.springframework.transaction.annotation.Transactional if missing) so JPQL/ORM
read optimizations apply while leaving groupMemberRepository.existsUserInGroup
and groupMemberRepository.findMemberInfo calls unchanged.
src/main/java/flipnote/group/adapter/in/web/MemberController.java (1)

43-43: TODO: 하위 권한 수정 항목이 해결되지 않았습니다.

권한 수정 기능이 필요하다면 별도 이슈로 추적할 것을 권장합니다.

해당 TODO를 별도 이슈로 등록하길 원하시면 구현 스켈레톤 생성을 도와드릴 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/adapter/in/web/MemberController.java` at line
43, The inline TODO "하위 권한 수정" in MemberController is unresolved; either track
it as a separate issue and replace the comment with a TODO referencing that
issue ID, or implement a minimal scaffold: add a controller endpoint stub (e.g.,
modifySubPermissions or updateSubPermissions) in MemberController that validates
the request DTO and delegates to a MemberService.updateSubPermissions method
(create the service method signature if missing) so the intent is explicit and
testable; include input validation and a TODO comment in the new stub linking to
the issue if full implementation is deferred.
src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java (3)

39-46: 비즈니스 규칙 위반에 IllegalArgumentException 대신 도메인 전용 예외 사용을 권장합니다.

IllegalArgumentException은 메서드에 잘못된 인자가 전달되는 프로그래밍 오류를 나타내는 JDK 표준 예외입니다. "사용자가 그룹 내에 없음"은 비즈니스 규칙 위반이므로, 도메인 레이어에 별도의 예외 클래스(UserNotInGroupException 등)를 정의하여 사용하면 오류 유형을 명확히 구분할 수 있고, 상위 레이어의 예외 처리(@ExceptionHandler) 도 일관성 있게 유지할 수 있습니다.

♻️ 리팩토링 제안

도메인 레이어에 예외 클래스 추가 (예: src/main/java/flipnote/group/domain/exception/UserNotInGroupException.java):

public class UserNotInGroupException extends RuntimeException {
    public UserNotInGroupException() {
        super("user not in Group");
    }
}

어댑터에서 교체:

 if (!isMember) {
-    throw new IllegalArgumentException("user not in Group");
+    throw new UserNotInGroupException();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`
around lines 39 - 46, Replace the use of IllegalArgumentException in
GroupMemberRepositoryAdapter.existsUserInGroup with a domain-specific runtime
exception: create a UserNotInGroupException in the domain layer (e.g., extend
RuntimeException with a default message "user not in Group"), then change the
method so that when groupMemberRepository.existsByGroupIdAndUserId(groupId,
userId) is false it throws new UserNotInGroupException(); keep the boolean check
and repository call intact but swap the thrown exception to the new domain
exception so upper layers can handle this business-rule violation consistently.

54-60: 중간 변수 memberInfo 제거로 코드를 간결하게 만들 수 있습니다.

♻️ 리팩토링 제안
 `@Override`
 public List<MemberInfo> findMemberInfo(Long groupId) {
     List<GroupMemberEntity> entities = groupMemberRepository.findAllByGroupId(groupId);
-
-    List<MemberInfo> memberInfo = GroupMemberMapper.toMemberInfo(entities);
-
-    return memberInfo;
+    return GroupMemberMapper.toMemberInfo(entities);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`
around lines 54 - 60, The method findMemberInfo in GroupMemberRepositoryAdapter
unnecessarily stores the result in a local variable memberInfo; remove that
intermediate variable and return GroupMemberMapper.toMemberInfo(...) directly
(either by passing the entities list from
groupMemberRepository.findAllByGroupId(groupId) or by keeping the entities local
and returning GroupMemberMapper.toMemberInfo(entities) inline) so the method
becomes a single return statement that calls GroupMemberMapper.toMemberInfo on
the results from groupMemberRepository.findAllByGroupId.

20-20: GroupMemberRepositoryRepository 인터페이스 이름에서 "Repository"가 중복됩니다.

인프라 레이어의 JPA 리포지토리 인터페이스 이름(GroupMemberRepositoryRepository)에 "Repository"가 이중으로 포함되어 있습니다. 같은 패키지의 다른 리포지토리들(GroupRoleRepository, GroupRolePermissionRepository)과의 명명 규칙 일관성을 위해 GroupMemberJpaRepository 또는 GroupMemberRepository로 개선하는 것을 권장합니다. 코드베이스에 2곳에만 참조되어 있으므로 리팩토링 비용이 낮습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`
at line 20, The interface name GroupMemberRepositoryRepository is redundant;
rename the JPA repository interface to a consistent name (e.g.,
GroupMemberJpaRepository or GroupMemberRepository) and update all usages
(including the field in GroupMemberRepositoryAdapter and the other reference
site) to the new type name; ensure imports and constructor/injection points in
GroupMemberRepositoryAdapter reflect the renamed interface and run compilation
to catch any remaining references, aligning naming with GroupRoleRepository and
GroupRolePermissionRepository.
src/main/java/flipnote/group/api/dto/request/GroupListRequestDto.java (1)

16-19: getPageRequest() 오버라이드가 부모의 sortBy/order 필드를 무시합니다.

하드코딩된 정렬(Sort.by(Sort.Direction.DESC, "id"))로 인해 CursorPagingRequest에서 상속받은 sortByorder 필드가 이 DTO에서는 의미 없는 값이 됩니다. 의도된 설계라면 괜찮지만, 커서 기반 페이지네이션에서 PageRequest가 실제로 사용되는 곳이 있는지 확인해 보세요. 사용되지 않는다면 오버라이드 자체가 불필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/api/dto/request/GroupListRequestDto.java` around
lines 16 - 19, getPageRequest() currently hardcodes Sort.by(Sort.Direction.DESC,
"id") and thus ignores the inherited sortBy and order fields from
CursorPagingRequest; either remove this override if PageRequest isn't actually
used for cursor pagination, or change getPageRequest() to respect the parent
fields by building the PageRequest using getSize() and Sort.by(convert order and
sortBy into Sort.Direction and property) (reference the getPageRequest() method,
the sortBy and order fields from CursorPagingRequest, and the
PageRequest.of(...) call to locate the code).
src/main/java/flipnote/group/domain/model/group/GroupInfo.java (1)

9-11: from 팩토리 메서드가 생성자와 동일하여 불필요합니다.

레코드의 canonical 생성자와 동일한 파라미터를 받아 그대로 전달만 하고 있어, 별도의 변환이나 검증 로직이 없다면 이 팩토리 메서드는 중복됩니다. 호출부에서 new GroupInfo(...) 를 직접 사용하거나, 향후 변환/검증 로직이 추가될 예정이라면 그대로 유지해도 무방합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/domain/model/group/GroupInfo.java` around lines
9 - 11, The static factory method from in the GroupInfo record is redundant
because it simply forwards to the canonical constructor; remove the from(Long
groupId, String name, String description, Category category, Long imageRefId)
method from the GroupInfo record and update all callers to use the record's
canonical constructor new GroupInfo(...) instead, or if you prefer to keep a
factory for future validation, replace the body with the intended
validation/translation logic inside GroupInfo.from before constructing the
record.
src/main/java/flipnote/group/api/dto/request/CursorPagingRequest.java (1)

26-41: normalized.isEmpty() 체크가 중복됩니다.

StringUtils.hasText(cursor)는 이미 null, 빈 문자열, 공백만 있는 문자열을 모두 false로 처리합니다. hasTexttrue를 반환한 경우 cursor.trim()의 결과가 빈 문자열일 수 없으므로 Line 32-33의 체크는 불필요합니다.

♻️ 중복 체크 제거 제안
 public Long getCursorId() {
 	if (!StringUtils.hasText(cursor)) {
 		return null;
 	}
 
-	final String normalized = cursor.trim();
-	if (normalized.isEmpty()) {
-		return null;
-	}
-
 	try {
-		return Long.valueOf(normalized);
+		return Long.valueOf(cursor.trim());
 	} catch (NumberFormatException ex) {
 		return null;
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/api/dto/request/CursorPagingRequest.java` around
lines 26 - 41, The getCursorId method currently redundantly checks
normalized.isEmpty() after StringUtils.hasText(cursor); remove the duplicate
check (lines that return null when normalized.isEmpty()) and simply trim the
cursor and parse it, keeping the try/catch for NumberFormatException; update the
method in CursorPagingRequest to rely on StringUtils.hasText(cursor) and then do
Long.valueOf(cursor.trim()) (or use the existing normalized variable) so
behavior remains the same but without the unnecessary empty-string branch.
src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryCustom.java (1)

8-13: 파라미터 네이밍이 메서드 간에 일관성이 없습니다.

세 메서드가 동일한 역할의 파라미터를 서로 다른 이름으로 사용하고 있습니다:

  • 커서 ID: lastId (메서드 1, 2) vs cursorId (메서드 3)
  • 페이지 크기: pageSize (메서드 1, 2) vs size (메서드 3)
  • 사용자 ID: userId (메서드 2) vs id (메서드 3)

특히 id는 어떤 엔티티의 ID인지 모호합니다. 일관된 네이밍을 적용해 주세요.

♻️ 일관된 네이밍 제안
 public interface GroupRepositoryCustom {
 	List<GroupInfo> findAllByCursor(Long cursorId, Category category, int pageSize);
 
 	List<GroupInfo> findAllByCursorAndUserId(Long cursorId, Category category, int pageSize, Long userId);
 
-	List<GroupInfo> findAllByCursorAndCreatedUserId(Long cursorId, Category category, int size, Long id);
+	List<GroupInfo> findAllByCursorAndCreatedUserId(Long cursorId, Category category, int pageSize, Long createdUserId);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryCustom.java`
around lines 8 - 13, The three methods use inconsistent parameter names; make
them consistent and explicit: use lastId for the cursor, pageSize for the page
size, and a clear user parameter name that matches the method intent (userId for
findAllByCursorAndUserId, createdUserId for findAllByCursorAndCreatedUserId).
Update the method signature of findAllByCursorAndCreatedUserId(Long cursorId,
Category category, int size, Long id) to findAllByCursorAndCreatedUserId(Long
lastId, Category category, int pageSize, Long createdUserId) and ensure
corresponding implementations/referrers use the same names.
src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java (1)

24-25: Q타입 필드를 private static final로 선언하세요

QGroupEntity.groupEntity는 이미 public static final 싱글턴이며 재사용해도 메모리와 초기화 비용을 절약할 수 있습니다. 현재 선언은 private도, final도 아니어서 의도치 않은 재할당 및 패키지 내 접근이 가능합니다.

♻️ 수정 제안
-QGroupEntity group = QGroupEntity.groupEntity;
-QGroupMemberEntity groupMember = QGroupMemberEntity.groupMemberEntity;
+private static final QGroupEntity group = QGroupEntity.groupEntity;
+private static final QGroupMemberEntity groupMember = QGroupMemberEntity.groupMemberEntity;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java`
around lines 24 - 25, The Q-type fields are declared as mutable instance
variables; change the declarations for the Q-type references (currently named
group and groupMember) to private static final and initialize them with
QGroupEntity.groupEntity and QGroupMemberEntity.groupMemberEntity respectively
so they reuse the existing public singletons and prevent reassignment and
unintended package access.
src/main/java/flipnote/group/application/port/in/FindGroupUseCase.java (1)

3-4: [선택적 개선] 인바운드 포트에서 웹 레이어 DTO 직접 참조

FindGroupUseCase 포트가 flipnote.group.api.dto 패키지에 속한 GroupListRequestDtoCursorPagingResponseDto를 직접 참조하고 있습니다. 헥사고날 아키텍처 관점에서 애플리케이션 포트(인바운드)는 웹 어댑터 전용 DTO에 의존하지 않고, 별도의 쿼리 커맨드 객체(FindAllGroupQuery 등)를 사용하는 것이 경계를 명확히 합니다. 현재 구조에서는 향후 non-HTTP 어댑터(예: gRPC) 추가 시 웹 DTO를 재사용해야 하는 부자연스러운 결합이 발생할 수 있습니다.

Also applies to: 12-16

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/application/port/in/FindGroupUseCase.java`
around lines 3 - 4, FindGroupUseCase currently depends on web-layer DTOs
(GroupListRequestDto, CursorPagingResponseDto); replace those web DTO types with
application-level query/response objects (e.g., create FindAllGroupQuery for
input and a port-level PagedGroupResult or FindGroupResult for output) and
update the FindGroupUseCase method signature(s) to accept the new query and
return the new port response type so the inbound port no longer imports
flipnote.group.api.dto; update any adapters/controllers to map between web DTOs
and the new query/response objects.
src/main/java/flipnote/group/application/service/FindGroupService.java (1)

49-57: findAllGroup에서 userId 파라미터가 사용되지 않습니다

userId를 파라미터로 받지만 groupRepository.findAllByCursor(...) 호출 시 전달하지 않습니다. 전체 그룹 조회에서 인증/인가 목적으로 남겨둔 것이라면 주석으로 의도를 명시하고, 현재 불필요하다면 제거하는 것이 명확합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/application/service/FindGroupService.java`
around lines 49 - 57, The userId parameter in findAllGroup(Long userId,
GroupListRequestDto req) is unused; either pass it into the repository call or
remove it/comment intent. If the service should scope results by user, update
the call to groupRepository.findAllByCursor(...) to include userId (adjust
repository/findAllByCursor signature and callers accordingly) so the method uses
user context; otherwise remove the userId parameter from findAllGroup (and
update callers) or add a clear comment above the method explaining why userId is
intentionally unused for authorization/telemetry purposes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/flipnote/group/adapter/in/web/GroupQueryController.java`:
- Around line 17-20: GroupQueryController shares the same
`@RequestMapping`("/v1/groups") base path as GroupController which can cause
routing conflicts; change the base path on GroupQueryController (the class
annotated with `@RestController` and `@RequiredArgsConstructor`) to a distinct
prefix such as `@RequestMapping`("/v1/groups/list") and update its handler method
mappings accordingly (or alternatively consolidate GroupQueryController into
GroupController by moving its methods into the existing GroupController class)
so that methods like the list endpoints no longer collide with GroupController's
/v1/groups/{groupId} routes.

In `@src/main/java/flipnote/group/adapter/in/web/InvitationController.java`:
- Line 4: Fix the typo in the TODO comment inside InvitationController (class
InvitationController) by replacing "가인" with "가입" so the comment reads "//todo
가입 신청 요청"; update any matching TODO occurrences in that file to keep wording
consistent.
- Around line 1-3: Add Spring MVC annotations to register the controller:
annotate the InvitationController class with `@RestController` and a class-level
`@RequestMapping` (e.g. "/invitations" or another suitable base path) so Spring
will detect and route requests to it; ensure you import
org.springframework.web.bind.annotation.RestController and
org.springframework.web.bind.annotation.RequestMapping and place the annotations
directly above the public class InvitationController declaration.

In `@src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java`:
- Around line 46-48: The repository method
GroupMemberRepositoryRepository.findAllByGroupId currently triggers N+1 when
GroupMemberMapper.toMemberInfo accesses entity.getRole().getRole(); fix by
eager-loading role in the query — either annotate findAllByGroupId with
`@EntityGraph`(attributePaths = "role") or replace it with a JPQL fetch-join query
(SELECT m FROM GroupMemberEntity m JOIN FETCH m.role WHERE m.groupId = :groupId)
so the RoleEntity referenced by GroupMemberEntity.role is loaded in the same
query.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`:
- Around line 48-51: Javadoc "그룹 멤버 아이디 조회"는 실제 반환값과 맞지 않습니다; 수정하려는 Javadoc을
GroupMemberRepositoryAdapter 클래스의 해당 메서드(현재 주석 위에 있는 메서드)를 찾아 반환형이 MemberInfo(예:
userId와 role 등을 포함하는 객체)의 목록임을 명시하도록 변경하세요 — 예: "그룹 멤버 목록 반환 (MemberInfo: userId
및 role 포함)" 및 `@return에` List<MemberInfo> 설명을 추가해 실제 동작과 주석이 일치하도록 만드세요.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java`:
- Around line 60-63: The method
GroupRepositoryAdapter.findAllByCursorAndCreatedUserId currently calls itself
causing a StackOverflowError; update the implementation to delegate to the
injected repository (groupRepository.findAllByCursorAndCreatedUserId) using the
same parameters (cursorId, category, size, userId) instead of calling
this.findAllByCursorAndCreatedUserId; ensure you reference the groupRepository
field and return its result.

In
`@src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java`:
- Around line 24-35: The toDomain method in GroupMemberMapper is missing mapping
for userId, causing GroupMember.userId to be null; update the
GroupMember.builder() call inside toDomain(GroupMemberEntity entity) to include
.userId(entity.getUserId()) (using GroupMemberEntity.getUserId() and the
GroupMember builder) so the domain object properly preserves the userId field.
- Around line 47-52: The mapping pipeline in GroupMemberMapper calls
entity.getRole().getRole() which can NPE if entity.getRole() is null; update the
stream mapping to guard against null role by either filtering out entities with
null getRole() before mapping or by supplying a safe fallback when building
MemberInfo (e.g., use a default role string or enum constant) and include a
clear comment/log or throw an IllegalStateException with context if null should
be impossible; change the lambda in the map (the MemberInfo.builder() call) to
reference entity.getRole() safely (null-check or Optional) instead of calling
getRole() directly.

In `@src/main/java/flipnote/group/api/dto/request/CursorPagingRequest.java`:
- Around line 18-20: The size field in CursorPagingRequest is declared as boxed
Integer with a default of 10, so if a caller sets it to null the
PageRequest.of(0, size) call (used in the class) will NPE on unboxing; fix by
making size a primitive int (private int size = 10) or, if boxed is required,
add `@NotNull` to the field and/or make the accessor defensive (e.g., getSize()
returns size == null ? 10 : size) and ensure any setter enforces non-null to
prevent null being passed into PageRequest.of; update references to
CursorPagingRequest.size/getSize accordingly.

In `@src/main/java/flipnote/group/api/dto/response/CursorPagingResponseDto.java`:
- Around line 13-15: The static factory method CursorPagingResponseDto.of should
defensively handle a null content list to avoid a NullPointerException: update
CursorPagingResponseDto.of(List<T> content, boolean hasNext, String nextCursor)
to treat a null content as an empty list (e.g., use Collections.emptyList() or
new ArrayList<>()) and compute size with a null-safe expression (content == null
? 0 : content.size()), then pass the normalized list, hasNext, and (hasNext ?
nextCursor : null) into the constructor so content is never null and size is
correct.

In
`@src/main/java/flipnote/group/application/port/in/result/FindAllGroupResult.java`:
- Around line 1-8: Remove the unused record FindAllGroupResult by deleting the
record declaration (FindAllGroupResult) and its file; ensure there are no
remaining imports or references (the service layer already returns
CursorPagingResponseDto<GroupInfo>), and run a project build/IDE search for
FindAllGroupResult to confirm no usages remain before committing.

In
`@src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java`:
- Line 5: The port interface GroupMemberRepositoryPort currently imports and
uses the persistence type RoleEntity (violating hexagonal boundaries); change
the save(...) signature to accept the domain type GroupMemberRole instead and
remove the RoleEntity import, then propagate this change by updating
GroupRoleRepositoryPort.create() return type and any imports/usages in
CreateGroupService to use GroupMemberRole; implement the conversion from
GroupMemberRole to RoleEntity inside the adapter GroupMemberRepositoryAdapter
(adapter should map domain GroupMemberRole -> RoleEntity before persisting) so
no persistence types leak into the application.port.out layer.

In
`@src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java`:
- Around line 3-7: The port GroupRoleRepositoryPort currently returns the
adapter-level RoleEntity which breaks hexagonal layering; change the create(Long
groupId) signature to return the domain type GroupMemberRole (or a new domain
VO) instead of RoleEntity, then update the out-adapter implementation to map
RoleEntity <-> GroupMemberRole when persisting/reading so the
application.port.out layer depends only on domain models (ensure implementations
of GroupRoleRepositoryPort perform the conversion).

In `@src/main/java/flipnote/group/application/service/CreateGroupService.java`:
- Line 6: CreateGroupService currently depends on persistence layer type
RoleEntity (import flipnote.group.adapter.out.entity.RoleEntity) which breaks
hexagonal boundaries; change the port method signatures
GroupRoleRepositoryPort.create(...) and GroupMemberRepositoryPort.save(...) to
use the domain enum/type GroupMemberRole instead of RoleEntity, update
CreateGroupService to pass GroupMemberRole values (and remove the RoleEntity
import), and implement mapping in the adapter implementations (convert
GroupMemberRole ↔ RoleEntity within the adapter) so infrastructure handles JPA
types while application and domain layers remain free of RoleEntity.

In
`@src/main/java/flipnote/group/application/service/FindGroupMemberService.java`:
- Line 28: The repository method
groupMemberRepository.existsUserInGroup(cmd.groupId(), cmd.userId()) is defined
to return void while its name implies a boolean; either rename the repository
method to assertUserInGroup or validateUserMembership (to keep the current
throw-on-missing behavior) and update FindGroupMemberService to call that
renamed method, or change existsUserInGroup to return boolean and update
FindGroupMemberService to check the boolean and throw the appropriate exception
itself; locate usages in FindGroupMemberService and the GroupMemberRepository
interface (existsUserInGroup) and make the corresponding renames/return-type
change so the method name matches its behavior.

In `@src/main/java/flipnote/group/domain/model/member/GroupMember.java`:
- Line 3: GroupMember currently depends on the JPA entity RoleEntity; remove
that dependency by replacing any RoleEntity references in GroupMember with a
domain-level representation (e.g., a new GroupMemberRole enum or a Role value
object) and update GroupMember's fields, constructors, getters/setters to use
GroupMemberRole instead of RoleEntity; then move conversion logic between
RoleEntity and GroupMemberRole into the adapter/mapper (e.g., GroupMemberMapper
or the repository/mapper in the adapter.out layer) so the domain model
(GroupMember) no longer imports or references RoleEntity.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`:
- Around line 11-12: The interface name GroupMemberRepositoryRepository is
duplicated and should be renamed; update the interface declaration (currently
extending JpaRepository<GroupMemberEntity, Long>) to a clearer name such as
GroupMemberJpaRepository or GroupMemberRepository, and adjust any imports/usages
referencing GroupMemberRepositoryRepository to the new interface name so
compilation and references to JpaRepository<GroupMemberEntity, Long> remain
correct.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java`:
- Around line 6-8: GroupRepository 인터페이스와 GroupRepositoryImpl 구현이 서로 다른 패키지(jpa
vs querydsl)에 있어 Spring Data JPA가 Impl을 못찾을 수 있습니다; 해결하려면 설정 클래스를 열어
`@EnableJpaRepositories`(basePackages =
"flipnote.group.infrastructure.persistence") 어노테이션을 추가해 리포지토리 스캔 범위를 명시하거나, 더
간단히 GroupRepositoryImpl을 GroupRepository와 동일한 패키지인
flipnote.group.infrastructure.persistence.jpa로 이동해 Spring이 기본 규칙으로 Impl을 자동으로
탐지하게 만드세요; 대상 심볼: GroupRepository, GroupRepositoryImpl, `@EnableJpaRepositories`,
패키지 flipnote.group.infrastructure.persistence(.jpa / .querydsl).

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java`:
- Around line 105-107: findAllByCursorAndCreatedUserId의 Javadoc이 실제 동작과 맞지 않으니
주석을 실제 기능으로 고치세요: 현재 메서드(findAllByCursorAndCreatedUserId, 클래스
GroupRepositoryImpl)는 "내가 생성한(OWNER) 그룹 목록을 커서 기반으로 조회"하므로 Javadoc 설명을 해당 동작으로
변경하고 파라미터 설명(예: lastId, size, createdUserId 등)과 반환값 설명을 정확히 기재하여 메서드의 목적과 입력·출력
정보를 일치시켜 주세요.
- Around line 73-103: In both repository methods findAllByCursorAndUserId and
findAllByCursorAndCreatedUserId, the query currently checks
group.deletedAt.isNull() but does not filter out soft-deleted group members;
update the BooleanBuilder/where clause in findAllByCursorAndUserId to include
groupMember.deletedAt.isNull() (e.g., where.and(groupMember.deletedAt.isNull()))
and likewise add the same groupMember.deletedAt.isNull() condition into the
.where(...) for findAllByCursorAndCreatedUserId so only active (non-deleted)
GroupMemberEntity rows are returned.

---

Outside diff comments:
In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`:
- Around line 33-36: In GroupMemberRepositoryAdapter, locate the Javadoc block
that documents `@param` groupId and `@param` userId for the method whose return type
was changed to void and remove the stray `@return` tag; ensure the Javadoc now
only contains the descriptive text and the `@param` tags (and update the
description if necessary) so it correctly matches the void method signature.

---

Nitpick comments:
In `@src/main/java/flipnote/group/adapter/in/web/MemberController.java`:
- Line 43: The inline TODO "하위 권한 수정" in MemberController is unresolved; either
track it as a separate issue and replace the comment with a TODO referencing
that issue ID, or implement a minimal scaffold: add a controller endpoint stub
(e.g., modifySubPermissions or updateSubPermissions) in MemberController that
validates the request DTO and delegates to a MemberService.updateSubPermissions
method (create the service method signature if missing) so the intent is
explicit and testable; include input validation and a TODO comment in the new
stub linking to the issue if full implementation is deferred.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`:
- Around line 39-46: Replace the use of IllegalArgumentException in
GroupMemberRepositoryAdapter.existsUserInGroup with a domain-specific runtime
exception: create a UserNotInGroupException in the domain layer (e.g., extend
RuntimeException with a default message "user not in Group"), then change the
method so that when groupMemberRepository.existsByGroupIdAndUserId(groupId,
userId) is false it throws new UserNotInGroupException(); keep the boolean check
and repository call intact but swap the thrown exception to the new domain
exception so upper layers can handle this business-rule violation consistently.
- Around line 54-60: The method findMemberInfo in GroupMemberRepositoryAdapter
unnecessarily stores the result in a local variable memberInfo; remove that
intermediate variable and return GroupMemberMapper.toMemberInfo(...) directly
(either by passing the entities list from
groupMemberRepository.findAllByGroupId(groupId) or by keeping the entities local
and returning GroupMemberMapper.toMemberInfo(entities) inline) so the method
becomes a single return statement that calls GroupMemberMapper.toMemberInfo on
the results from groupMemberRepository.findAllByGroupId.
- Line 20: The interface name GroupMemberRepositoryRepository is redundant;
rename the JPA repository interface to a consistent name (e.g.,
GroupMemberJpaRepository or GroupMemberRepository) and update all usages
(including the field in GroupMemberRepositoryAdapter and the other reference
site) to the new type name; ensure imports and constructor/injection points in
GroupMemberRepositoryAdapter reflect the renamed interface and run compilation
to catch any remaining references, aligning naming with GroupRoleRepository and
GroupRolePermissionRepository.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java`:
- Around line 15-16: The import and type name GroupMemberRepositoryRepository is
duplicated ("Repository" twice); rename the interface to a clearer name such as
GroupMemberJpaRepository, update the import in GroupRoleRepositoryAdapter (and
any other files) to use the new name, and update all usages (field types,
constructor parameters, method calls) that reference
GroupMemberRepositoryRepository inside this class to the new symbol; perform the
refactor via your IDE (rename file and type) or update the interface file name
and all references so imports and types (e.g., the field/parameter where
GroupMemberRepositoryRepository is used) match the new GroupMemberJpaRepository
identifier.

In `@src/main/java/flipnote/group/api/dto/request/CursorPagingRequest.java`:
- Around line 26-41: The getCursorId method currently redundantly checks
normalized.isEmpty() after StringUtils.hasText(cursor); remove the duplicate
check (lines that return null when normalized.isEmpty()) and simply trim the
cursor and parse it, keeping the try/catch for NumberFormatException; update the
method in CursorPagingRequest to rely on StringUtils.hasText(cursor) and then do
Long.valueOf(cursor.trim()) (or use the existing normalized variable) so
behavior remains the same but without the unnecessary empty-string branch.

In `@src/main/java/flipnote/group/api/dto/request/GroupListRequestDto.java`:
- Around line 16-19: getPageRequest() currently hardcodes
Sort.by(Sort.Direction.DESC, "id") and thus ignores the inherited sortBy and
order fields from CursorPagingRequest; either remove this override if
PageRequest isn't actually used for cursor pagination, or change
getPageRequest() to respect the parent fields by building the PageRequest using
getSize() and Sort.by(convert order and sortBy into Sort.Direction and property)
(reference the getPageRequest() method, the sortBy and order fields from
CursorPagingRequest, and the PageRequest.of(...) call to locate the code).

In
`@src/main/java/flipnote/group/api/dto/response/FindGroupMemberResponseDto.java`:
- Around line 8-14: The response DTO currently exposes the domain type
MemberInfo; create a new API record MemberInfoDto (e.g., record
MemberInfoDto(Long userId, GroupMemberRole role) with a static from(MemberInfo))
and update FindGroupMemberResponseDto.from to convert result.memberInfoList()
into a List<MemberInfoDto> (mapping each MemberInfo via MemberInfoDto.from) so
FindGroupMemberResponseDto holds List<MemberInfoDto> instead of
List<MemberInfo>, leaving FindGroupMemberResult and other domain classes
untouched.

In `@src/main/java/flipnote/group/application/port/in/FindGroupUseCase.java`:
- Around line 3-4: FindGroupUseCase currently depends on web-layer DTOs
(GroupListRequestDto, CursorPagingResponseDto); replace those web DTO types with
application-level query/response objects (e.g., create FindAllGroupQuery for
input and a port-level PagedGroupResult or FindGroupResult for output) and
update the FindGroupUseCase method signature(s) to accept the new query and
return the new port response type so the inbound port no longer imports
flipnote.group.api.dto; update any adapters/controllers to map between web DTOs
and the new query/response objects.

In
`@src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java`:
- Line 11: The method signature void existsUserInGroup(Long groupId, Long
userId) is ambiguous at the port level; update the contract to make behavior
explicit by either (A) changing the return type to boolean (e.g.,
existsUserInGroup returns true/false) so adapters return existence, or (B)
renaming to assertUserInGroup and document/implement it to throw a specific
unchecked exception when the user is not a member. Add Javadoc to
GroupMemberRepositoryPort describing the chosen contract (return boolean vs
throw) so adapters implementing existsUserInGroup/assertUserInGroup know whether
to return a flag or throw an exception.

In
`@src/main/java/flipnote/group/application/service/FindGroupMemberService.java`:
- Around line 25-35: The findGroupMember method in FindGroupMemberService is a
read-only operation but lacks the `@Transactional`(readOnly = true) annotation;
update the FindGroupMemberService class by annotating the
findGroupMember(FindGroupMemberCommand cmd) method with `@Transactional`(readOnly
= true) (and add the import for
org.springframework.transaction.annotation.Transactional if missing) so JPQL/ORM
read optimizations apply while leaving groupMemberRepository.existsUserInGroup
and groupMemberRepository.findMemberInfo calls unchanged.

In `@src/main/java/flipnote/group/application/service/FindGroupService.java`:
- Around line 49-57: The userId parameter in findAllGroup(Long userId,
GroupListRequestDto req) is unused; either pass it into the repository call or
remove it/comment intent. If the service should scope results by user, update
the call to groupRepository.findAllByCursor(...) to include userId (adjust
repository/findAllByCursor signature and callers accordingly) so the method uses
user context; otherwise remove the userId parameter from findAllGroup (and
update callers) or add a clear comment above the method explaining why userId is
intentionally unused for authorization/telemetry purposes.

In `@src/main/java/flipnote/group/domain/model/group/GroupInfo.java`:
- Around line 9-11: The static factory method from in the GroupInfo record is
redundant because it simply forwards to the canonical constructor; remove the
from(Long groupId, String name, String description, Category category, Long
imageRefId) method from the GroupInfo record and update all callers to use the
record's canonical constructor new GroupInfo(...) instead, or if you prefer to
keep a factory for future validation, replace the body with the intended
validation/translation logic inside GroupInfo.from before constructing the
record.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`:
- Line 6: Remove the unused imports in GroupMemberRepositoryRepository: delete
the import org.springframework.data.jpa.repository.Query and the import
flipnote.group.adapter.out.entity.RoleEntity since they are not referenced in
the interface (GroupMemberRepositoryRepository); ensure no other code in that
file depends on Query or RoleEntity and run a quick compile to confirm
unused-import warnings are gone.

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryCustom.java`:
- Around line 8-13: The three methods use inconsistent parameter names; make
them consistent and explicit: use lastId for the cursor, pageSize for the page
size, and a clear user parameter name that matches the method intent (userId for
findAllByCursorAndUserId, createdUserId for findAllByCursorAndCreatedUserId).
Update the method signature of findAllByCursorAndCreatedUserId(Long cursorId,
Category category, int size, Long id) to findAllByCursorAndCreatedUserId(Long
lastId, Category category, int pageSize, Long createdUserId) and ensure
corresponding implementations/referrers use the same names.

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java`:
- Around line 24-25: The Q-type fields are declared as mutable instance
variables; change the declarations for the Q-type references (currently named
group and groupMember) to private static final and initialize them with
QGroupEntity.groupEntity and QGroupMemberEntity.groupMemberEntity respectively
so they reuse the existing public singletons and prevent reassignment and
unintended package access.

Comment on lines +17 to +20
@RequiredArgsConstructor
@RestController
@RequestMapping("/v1/groups")
public class GroupQueryController {
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

GroupController와 동일한 /v1/groups base path 사용 — 경로 충돌 가능성

GroupController에도 @RequestMapping("/v1/groups")가 있으며, GET /v1/groups/{groupId} 템플릿이 선언되어 있습니다. Spring MVC는 리터럴 세그먼트를 템플릿보다 우선하므로 /me, /created는 정상 라우팅되나, groupIdLong이 아닌 문자열 경로가 추가될 경우 우선순위 충돌이 발생할 수 있습니다.

두 컨트롤러가 같은 base path를 공유하는 현재 구조는 향후 엔드포인트 추가 시 실수를 유발할 수 있으므로, 목록 조회용 컨트롤러는 /v1/groups/list와 같이 별도 prefix를 사용하거나, 두 컨트롤러를 통합하는 것을 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/adapter/in/web/GroupQueryController.java` around
lines 17 - 20, GroupQueryController shares the same
`@RequestMapping`("/v1/groups") base path as GroupController which can cause
routing conflicts; change the base path on GroupQueryController (the class
annotated with `@RestController` and `@RequiredArgsConstructor`) to a distinct
prefix such as `@RequestMapping`("/v1/groups/list") and update its handler method
mappings accordingly (or alternatively consolidate GroupQueryController into
GroupController by moving its methods into the existing GroupController class)
so that methods like the list endpoints no longer collide with GroupController's
/v1/groups/{groupId} routes.

Comment on lines +1 to +3
package flipnote.group.adapter.in.web;

public class InvitationController {
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

@RestController@RequestMapping 어노테이션 누락

@RestController 어노테이션이 없으면 Spring이 이 클래스를 컨트롤러 빈으로 등록하지 않습니다. 추후 엔드포인트를 추가하더라도 요청이 라우팅되지 않으므로, 지금 단계에서 어노테이션 기본 틀을 잡아두는 것을 권장합니다.

🔧 어노테이션 추가 제안
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/v1/invitations")
+@RequiredArgsConstructor
 public class InvitationController {
📝 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
package flipnote.group.adapter.in.web;
public class InvitationController {
package flipnote.group.adapter.in.web;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
`@RestController`
`@RequestMapping`("/api/v1/invitations")
`@RequiredArgsConstructor`
public class InvitationController {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/adapter/in/web/InvitationController.java` around
lines 1 - 3, Add Spring MVC annotations to register the controller: annotate the
InvitationController class with `@RestController` and a class-level
`@RequestMapping` (e.g. "/invitations" or another suitable base path) so Spring
will detect and route requests to it; ensure you import
org.springframework.web.bind.annotation.RestController and
org.springframework.web.bind.annotation.RequestMapping and place the annotations
directly above the public class InvitationController declaration.

package flipnote.group.adapter.in.web;

public class InvitationController {
//todo 가인 신청 요청
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

오타: 가인가입

//todo 가인 신청 요청//todo 가입 신청 요청

✏️ 오타 수정
-	//todo 가인 신청 요청
+	//todo 가입 신청 요청
📝 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
//todo 가인 신청 요청
//todo 가입 신청 요청
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/adapter/in/web/InvitationController.java` at
line 4, Fix the typo in the TODO comment inside InvitationController (class
InvitationController) by replacing "가인" with "가입" so the comment reads "//todo
가입 신청 요청"; update any matching TODO occurrences in that file to keep wording
consistent.

Comment on lines +46 to +48
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_role_id", nullable = false)
private RoleEntity role;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. findAllByGroupId 메서드 사용 패턴 확인
echo "=== findAllByGroupId 사용 패턴 ==="
rg -n "findAllByGroupId" --type=java -C 5

# 2. GroupMemberRepository 파일 찾기
echo -e "\n=== GroupMemberRepository 파일 위치 ==="
find . -name "*GroupMemberRepository*" -type f 2>/dev/null

# 3. FindGroupMemberService 파일 찾기
echo -e "\n=== FindGroupMemberService 파일 위치 ==="
find . -name "*FindGroupMemberService*" -type f 2>/dev/null

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 2742


🏁 Script executed:

#!/bin/bash
# 1. 전체 GroupMemberRepositoryRepository 내용 확인
echo "=== GroupMemberRepositoryRepository 전체 ==="
cat -n src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java

# 2. GroupMemberMapper 확인 - role이 사용되는지 확인
echo -e "\n=== GroupMemberMapper 위치 ==="
find . -name "*GroupMemberMapper*" -type f 2>/dev/null

# 3. FindGroupMemberService 전체 내용
echo -e "\n=== FindGroupMemberService 전체 ==="
cat -n src/main/java/flipnote/group/application/service/FindGroupMemberService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 2304


🏁 Script executed:

#!/bin/bash
# 1. GroupMemberMapper 전체 확인
echo "=== GroupMemberMapper 전체 ==="
cat -n src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java

# 2. MemberInfo 도메인 모델 확인
echo -e "\n=== MemberInfo 위치 및 내용 ==="
find . -name "MemberInfo.java" -type f 2>/dev/null
find . -name "MemberInfo.java" -type f -exec cat -n {} \;

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 2540


🏁 Script executed:

#!/bin/bash
# Verify what RoleEntity contains (to understand the full query implications)
echo "=== RoleEntity 구조 확인 ==="
find . -name "RoleEntity.java" -type f -exec cat -n {} \;

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1826


@EntityGraph를 사용하거나 fetch join 쿼리로 N+1 쿼리 문제를 해결하세요.

GroupMemberRepositoryRepository.findAllByGroupId()는 현재 fetch 전략이 없어서 N+1 쿼리가 발생합니다. GroupMemberMapper.toMemberInfo()에서 각 멤버의 entity.getRole().getRole()을 접근하면, 초기 쿼리 1개 + 각 멤버당 role 로드 쿼리 N개가 실행됩니다.

findAllByGroupId() 메서드에 @EntityGraph(attributePaths = "role")을 추가하거나, @Query로 fetch join을 사용하여 한 번에 role을 로드하세요:

`@Query`("SELECT m FROM GroupMemberEntity m JOIN FETCH m.role WHERE m.groupId = :groupId")
List<GroupMemberEntity> findAllByGroupId(Long groupId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java`
around lines 46 - 48, The repository method
GroupMemberRepositoryRepository.findAllByGroupId currently triggers N+1 when
GroupMemberMapper.toMemberInfo accesses entity.getRole().getRole(); fix by
eager-loading role in the query — either annotate findAllByGroupId with
`@EntityGraph`(attributePaths = "role") or replace it with a JPQL fetch-join query
(SELECT m FROM GroupMemberEntity m JOIN FETCH m.role WHERE m.groupId = :groupId)
so the RoleEntity referenced by GroupMemberEntity.role is loaded in the same
query.

Comment on lines +48 to +51
/**
* 그룹 멤버 아이디 조회
* @param groupId
* @return
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

Javadoc 주석이 실제 동작과 다릅니다.

"그룹 멤버 아이디 조회"라고 되어 있지만, 실제로는 MemberInfo(userId + role 등) 목록을 반환합니다. 주석을 실제 반환 내용에 맞게 수정하세요.

📝 수정 제안
 /**
- * 그룹 멤버 아이디 조회
+ * 그룹 멤버 정보 조회
  * `@param` groupId
  * `@return`
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`
around lines 48 - 51, Javadoc "그룹 멤버 아이디 조회"는 실제 반환값과 맞지 않습니다; 수정하려는 Javadoc을
GroupMemberRepositoryAdapter 클래스의 해당 메서드(현재 주석 위에 있는 메서드)를 찾아 반환형이 MemberInfo(예:
userId와 role 등을 포함하는 객체)의 목록임을 명시하도록 변경하세요 — 예: "그룹 멤버 목록 반환 (MemberInfo: userId
및 role 포함)" 및 `@return에` List<MemberInfo> 설명을 추가해 실제 동작과 주석이 일치하도록 만드세요.


import flipnote.group.domain.model.group.GroupId;
import flipnote.group.domain.model.user.UserId;
import flipnote.group.adapter.out.entity.RoleEntity;
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

도메인 모델이 JPA 엔티티(RoleEntity)에 의존하고 있습니다 — 헥사고날 아키텍처의 핵심 원칙 위반.

GroupMember는 도메인 레이어에 위치하지만 adapter.out.entity.RoleEntity(JPA 엔티티)를 직접 참조하고 있습니다. 이는 도메인 레이어가 인프라/어댑터 레이어에 의존하게 되어 헥사고날 아키텍처의 의존성 방향 규칙을 위반합니다.

도메인 모델에서는 도메인 수준의 역할 표현(예: GroupMemberRole enum, 또는 role ID + role name을 가진 도메인 VO)을 사용하고, RoleEntity ↔ 도메인 모델 간 변환은 매퍼(adapter 레이어)에서 처리해야 합니다.

♻️ 도메인 모델 개선 예시
-import flipnote.group.adapter.out.entity.RoleEntity;
+import flipnote.group.domain.model.member.GroupMemberRole;

 `@Getter`
 `@NoArgsConstructor`(access = AccessLevel.PRIVATE)
 public class GroupMember {
 	private Long id;
 	private Long groupId;
 	private Long userId;
-	private RoleEntity role;
+	private Long roleId;
+	private GroupMemberRole role;

 	`@Builder`
-	private GroupMember(Long id, Long groupId, Long userId, RoleEntity role) {
+	private GroupMember(Long id, Long groupId, Long userId, Long roleId, GroupMemberRole role) {
 		this.id = id;
 		this.groupId = groupId;
 		this.userId = userId;
+		this.roleId = roleId;
 		this.role = role;
 	}
 }

Also applies to: 15-15

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/domain/model/member/GroupMember.java` at line 3,
GroupMember currently depends on the JPA entity RoleEntity; remove that
dependency by replacing any RoleEntity references in GroupMember with a
domain-level representation (e.g., a new GroupMemberRole enum or a Role value
object) and update GroupMember's fields, constructors, getters/setters to use
GroupMemberRole instead of RoleEntity; then move conversion logic between
RoleEntity and GroupMemberRole into the adapter/mapper (e.g., GroupMemberMapper
or the repository/mapper in the adapter.out layer) so the domain model
(GroupMember) no longer imports or references RoleEntity.

Comment on lines +11 to +12
public interface GroupMemberRepositoryRepository
extends JpaRepository<GroupMemberEntity, Long> {
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

GroupMemberRepositoryRepository — "Repository"가 중복된 이름입니다.

명명 오류로 보입니다. GroupMemberJpaRepository 또는 기존 GroupMemberRepository를 확장/대체하는 것이 적절합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepositoryRepository.java`
around lines 11 - 12, The interface name GroupMemberRepositoryRepository is
duplicated and should be renamed; update the interface declaration (currently
extending JpaRepository<GroupMemberEntity, Long>) to a clearer name such as
GroupMemberJpaRepository or GroupMemberRepository, and adjust any imports/usages
referencing GroupMemberRepositoryRepository to the new interface name so
compilation and references to JpaRepository<GroupMemberEntity, Long> remain
correct.

Comment on lines +6 to +8
import flipnote.group.infrastructure.persistence.querydsl.GroupRepositoryCustom;

public interface GroupRepository extends JpaRepository<GroupEntity, Long> {
public interface GroupRepository extends JpaRepository<GroupEntity, Long>, GroupRepositoryCustom {
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for GroupRepositoryImpl class definition
rg -n 'class GroupRepositoryImpl' --type=java

# Search for EnableJpaRepositories configuration
rg -n 'EnableJpaRepositories' --type=java

# Also check the full package structure to understand the layout
echo "=== Checking directory structure ==="
find . -type f -name "*GroupRepository*" | head -20

# Check if there's any Spring boot configuration that might affect component scanning
rg -n '@SpringBootApplication|@Configuration' --type=java | head -20

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 951


🏁 Script executed:

#!/bin/bash
# Check QuerydslConfig.java content
echo "=== QuerydslConfig.java ===" 
cat src/main/java/flipnote/group/infrastructure/config/QuerydslConfig.java

echo -e "\n=== GroupApplication.java ===" 
cat src/main/java/flipnote/group/GroupApplication.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 940


Spring Data JPA의 Custom Repository Impl 탐지 문제를 해결하세요.

GroupRepositoryjpa 패키지에, GroupRepositoryImplquerydsl 패키지에 위치하지만, 명시적인 @EnableJpaRepositories 설정이 없습니다. Spring Data JPA는 기본적으로 리포지토리 인터페이스와 동일한 패키지에서 Impl 클래스를 찾으므로, 이 구조에서는 GroupRepositoryImpl이 제대로 발견되지 않을 수 있습니다.

해결 방안:

  1. @EnableJpaRepositories(basePackages = "flipnote.group.infrastructure.persistence") 를 설정 클래스에 추가하여 명시적으로 패키지 범위를 지정하거나,
  2. GroupRepositoryImplGroupRepository와 동일한 jpa 패키지로 이동하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java`
around lines 6 - 8, GroupRepository 인터페이스와 GroupRepositoryImpl 구현이 서로 다른 패키지(jpa
vs querydsl)에 있어 Spring Data JPA가 Impl을 못찾을 수 있습니다; 해결하려면 설정 클래스를 열어
`@EnableJpaRepositories`(basePackages =
"flipnote.group.infrastructure.persistence") 어노테이션을 추가해 리포지토리 스캔 범위를 명시하거나, 더
간단히 GroupRepositoryImpl을 GroupRepository와 동일한 패키지인
flipnote.group.infrastructure.persistence.jpa로 이동해 Spring이 기본 규칙으로 Impl을 자동으로
탐지하게 만드세요; 대상 심볼: GroupRepository, GroupRepositoryImpl, `@EnableJpaRepositories`,
패키지 flipnote.group.infrastructure.persistence(.jpa / .querydsl).

Comment on lines +73 to +103
@Override
public List<GroupInfo> findAllByCursorAndUserId(Long lastId, Category category, int pageSize, Long userId) {

BooleanBuilder where = new BooleanBuilder()
.and(group.deletedAt.isNull())
.and(groupMember.userId.eq(userId));

if (lastId != null) {
where.and(group.id.lt(lastId));
}

if (category != null) {
where.and(group.category.eq(category));
}

return queryFactory
.select(Projections.constructor(
GroupInfo.class,
group.id,
group.name,
group.description,
group.category,
group.imageRefId
))
.from(groupMember)
.join(group).on(group.id.eq(groupMember.groupId))
.where(where)
.orderBy(group.id.desc())
.limit(pageSize + 1)
.fetch();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -e java "GroupMemberEntity.java" --exec cat {}

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1826


🏁 Script executed:

fd -e java "BaseEntity.java" --exec cat {}

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1058


🏁 Script executed:

fd -e java "GroupRepositoryImpl.java" --exec cat {}

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 3313


findAllByCursorAndUserIdfindAllByCursorAndCreatedUserId에서 탈퇴한 그룹 멤버가 필터링되지 않음

group.deletedAt.isNull()은 체크하지만 groupMember.deletedAt.isNull() 필터가 없습니다. GroupMemberEntity는 BaseEntity를 상속받아 deletedAt 필드를 가지고 있으므로, 이미 탈퇴한 멤버의 그룹도 조회 결과에 포함됩니다.

  • findAllByCursorAndUserId (73-103): where.and(groupMember.deletedAt.isNull()) 추가 필요
  • findAllByCursorAndCreatedUserId (120+): .where() 조건에 groupMember.deletedAt.isNull() 추가 필요
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java`
around lines 73 - 103, In both repository methods findAllByCursorAndUserId and
findAllByCursorAndCreatedUserId, the query currently checks
group.deletedAt.isNull() but does not filter out soft-deleted group members;
update the BooleanBuilder/where clause in findAllByCursorAndUserId to include
groupMember.deletedAt.isNull() (e.g., where.and(groupMember.deletedAt.isNull()))
and likewise add the same groupMember.deletedAt.isNull() condition into the
.where(...) for findAllByCursorAndCreatedUserId so only active (non-deleted)
GroupMemberEntity rows are returned.

Comment on lines +105 to +107
/**
* 그룹 테이블에 생성한 유저 추가
* @param lastId
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

Javadoc 주석이 메서드 동작과 다릅니다

findAllByCursorAndCreatedUserId의 Javadoc에 "그룹 테이블에 생성한 유저 추가"라고 기술되어 있으나, 이 메서드는 내가 생성한(OWNER) 그룹 목록을 조회하는 메서드입니다.

📝 수정 제안
 /**
- * 그룹 테이블에 생성한 유저 추가
+ * 내가 생성한 그룹 전체 조회
  * `@param` lastId
  * `@param` category
  * `@param` pageSize
  * `@param` userId
  * `@return`
  */
📝 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
/**
* 그룹 테이블에 생성한 유저 추가
* @param lastId
/**
* 내가 생성한 그룹 전체 조회
* `@param` lastId
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepositoryImpl.java`
around lines 105 - 107, findAllByCursorAndCreatedUserId의 Javadoc이 실제 동작과 맞지 않으니
주석을 실제 기능으로 고치세요: 현재 메서드(findAllByCursorAndCreatedUserId, 클래스
GroupRepositoryImpl)는 "내가 생성한(OWNER) 그룹 목록을 커서 기반으로 조회"하므로 Javadoc 설명을 해당 동작으로
변경하고 파라미터 설명(예: lastId, size, createdUserId 등)과 반환값 설명을 정확히 기재하여 메서드의 목적과 입력·출력
정보를 일치시켜 주세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant