Skip to content

Feat: [FN-302] 그룹 권한 추가#6

Merged
stoneTiger0912 merged 13 commits intomainfrom
feat/add-member-role
Feb 18, 2026
Merged

Feat: [FN-302] 그룹 권한 추가#6
stoneTiger0912 merged 13 commits intomainfrom
feat/add-member-role

Conversation

@stoneTiger0912
Copy link
Member

@stoneTiger0912 stoneTiger0912 commented Feb 13, 2026

Summary by CodeRabbit

  • New Features

    • 권한 항목(KICK, JOIN_REQUEST_MANAGE) 및 역할-권한 매핑을 DB에서 관리하도록 도입했습니다.
    • 그룹 생성 시 기본 역할들과 해당 권한들이 자동으로 생성됩니다.
    • 그룹 멤버 존재 여부 및 역할 확인용 조회 기능이 추가되었습니다.
  • Refactor

    • 멤버 저장 흐름이 역할 ID를 명시하는 방식으로 일반화되었습니다.
    • 그룹 조회·변경·삭제 시 소유자/멤버 검증이 추가되어 권한 확인이 강제됩니다.
  • Removed

    • 멤버 역할 목록에서 STAFF 역할이 제거되었습니다.

@stoneTiger0912 stoneTiger0912 self-assigned this Feb 13, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

그룹 멤버 권한 모델을 enum 기반에서 DB 관리형 역할/권한 모델로 전환합니다. GroupMemberEntity가 role 대신 groupRoleId를 사용하도록 변경되고 RoleEntity·PermissionEntity와 관련 리포지토·포트·매퍼·서비스에서 역할 생성·검증 로직이 추가되었습니다.

Changes

Cohort / File(s) Summary
Entity layer
src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java, src/main/java/flipnote/group/adapter/out/entity/RoleEntity.java, src/main/java/flipnote/group/adapter/out/entity/PermissionEntity.java
GroupMemberEntityrole: GroupMemberRolegroupRoleId: Long으로 변경; RoleEntity, PermissionEntity 신규 JPA 엔티티 및 팩토리 메서드 추가.
Repository adapters & JPA
src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java, src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java, src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java, src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRoleRepository.java, src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRolePermissionRepository.java
멤버 저장 API를 roleId 인자로 받는 일반화된 save(...)로 변경; existsUserInGroup 추가; 신규 GroupRoleRepositoryAdapter로 역할·권한 생성 및 checkRole 구현; JPA 리포지토리 인터페이스들에 조회/존재 체크 메서드 추가/신규 리포지토 추가.
Mapper layer
src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java
createOwner(...) 제거, 파라미터화된 create(groupId, userId, roleId) 추가(엔티티 생성 위임).
Ports / Interfaces
src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java, src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java
포트 API 변경: saveOwnersave(groupId,userId,roleId)로 대체; existsUserInGroup 추가; 신규 GroupRoleRepositoryPort 추가(roles 생성 및 checkRole).
Service layer
src/main/java/flipnote/group/application/service/CreateGroupService.java, src/main/java/flipnote/group/application/service/ChangeGroupService.java, src/main/java/flipnote/group/application/service/DeleteGroupService.java, src/main/java/flipnote/group/application/service/FindGroupService.java
그룹 생성 시 역할 생성 호출 및 owner roleId 할당; 변경·삭제·조회 서비스들에 소유권·멤버십 검증 로직 추가(checkRole, existsUserInGroup).
Domain model
src/main/java/flipnote/group/domain/model/member/GroupMemberRole.java, src/main/java/flipnote/group/domain/model/permission/GroupPermission.java
GroupMemberRole에서 STAFF 제거; 신규 GroupPermission enum 추가(KICK, JOIN_REQUEST_MANAGE).
Controller / Other
src/main/java/flipnote/group/adapter/in/web/GroupController.java
클래스 말미에 TODO 주석(하위 권한 수정) 추가(로직 변경 없음).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CreateGroupService
    participant GroupRepository
    participant GroupRoleRepositoryAdapter
    participant RoleRepository
    participant GroupRolePermissionRepository
    participant GroupMemberRepositoryAdapter
    participant GroupMemberRepository

    User->>CreateGroupService: 그룹 생성 요청
    CreateGroupService->>GroupRepository: 그룹 저장
    GroupRepository-->>CreateGroupService: groupId
    CreateGroupService->>GroupRoleRepositoryAdapter: create(groupId)
    GroupRoleRepositoryAdapter->>RoleRepository: save RoleEntity(OWNER/HEAD_MANAGER/MANAGER/MEMBER)
    RoleRepository-->>GroupRoleRepositoryAdapter: roleIds
    GroupRoleRepositoryAdapter->>GroupRolePermissionRepository: save PermissionEntity들
    GroupRolePermissionRepository-->>GroupRoleRepositoryAdapter: permissionIds
    GroupRoleRepositoryAdapter-->>CreateGroupService: ownerRoleId
    CreateGroupService->>GroupMemberRepositoryAdapter: save(groupId, userId, ownerRoleId)
    GroupMemberRepositoryAdapter->>GroupMemberRepository: save GroupMemberEntity
    GroupMemberRepository-->>GroupMemberRepositoryAdapter: 완료
    GroupMemberRepositoryAdapter-->>CreateGroupService: 완료
    CreateGroupService-->>User: 그룹 생성 완료
Loading
sequenceDiagram
    actor User
    participant ChangeGroupService
    participant GroupRoleRepositoryAdapter
    participant RoleRepository
    participant GroupMemberRepository
    participant GroupRepository

    User->>ChangeGroupService: 그룹 수정 요청
    ChangeGroupService->>GroupRoleRepositoryAdapter: checkRole(userId, groupId, OWNER)
    GroupRoleRepositoryAdapter->>RoleRepository: findByGroupIdAndRole(groupId, OWNER)
    RoleRepository-->>GroupRoleRepositoryAdapter: roleEntity
    GroupRoleRepositoryAdapter->>GroupMemberRepository: existsByUserIdAndGroupRoleId(userId, roleId)
    GroupMemberRepository-->>GroupRoleRepositoryAdapter: 존재 여부
    alt owner
        GroupRoleRepositoryAdapter-->>ChangeGroupService: true
        ChangeGroupService->>GroupRepository: 그룹 수정 수행
        GroupRepository-->>ChangeGroupService: 수정 완료
        ChangeGroupService-->>User: 수정 성공
    else not owner
        GroupRoleRepositoryAdapter-->>ChangeGroupService: false
        ChangeGroupService-->>User: IllegalArgumentException("not owner")
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 흙을 파고 역할 씨앗 심었네
OWNER는 반짝, MEMBER는 포근히
권한은 당근길에 표시를 남기고
새 그룹밭에 씨앗 하나 톡 떨어뜨려
깡총깡총, 코드도 춤추네 🌱

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 풀 리퀘스트 제목이 변경사항의 주요 내용을 명확하게 요약하고 있습니다. 그룹 권한 관련 기능 추가라는 핵심 변경사항을 정확하게 반영하고 있습니다.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-member-role

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

Caution

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

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

6-6: ⚠️ Potential issue | 🟡 Minor

사용되지 않는 GroupMemberRole import가 남아 있습니다.

enum 기반 역할에서 Long roleId로 마이그레이션했으므로 이 import는 더 이상 필요하지 않습니다. EnumType, Enumerated import도 확인이 필요합니다.

♻️ 수정 제안
-import flipnote.group.domain.model.member.GroupMemberRole;
src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java (1)

4-8: ⚠️ Potential issue | 🟡 Minor

사용되지 않는 import 3개가 남아 있습니다.

enum 기반 역할에서 Long groupRoleId로 마이그레이션했으므로 GroupMemberRole, EnumType, Enumerated import가 더 이상 필요하지 않습니다.

♻️ 수정 제안
-import flipnote.group.domain.model.member.GroupMemberRole;
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
 import jakarta.persistence.GeneratedValue;
🤖 Fix all issues with AI agents
In `@src/main/java/flipnote/group/adapter/out/entity/RoleEntity.java`:
- Around line 19-24: RoleEntity에 선언된 `@Table의` uniqueConstraints에서 columnNames에
잘못된 컬럼명을 사용하고 제약 이름이 혼동을 일으킵니다; update the uniqueConstraints in the `@Table`
annotation to use the actual DB column name "group_role" instead of "role" and
rename the constraint from "uk_group_roles_group_name" to a clearer name like
"uk_group_roles_group_role" (or similar). Locate the `@Table`(...) on the
RoleEntity class and the `@Column`(name = "group_role") declaration to ensure the
columnNames array matches "group_role" and the constraint name reflects the
columns it covers.

In `@src/main/java/flipnote/group/application/service/ChangeGroupService.java`:
- Line 20: ChangeGroupService currently depends on the JPA interface field
jpaGroupRepository (type GroupRepository); refactor it to depend on the
abstraction GroupRepositoryPort instead: replace the field jpaGroupRepository
with a GroupRepositoryPort-typed field, update the constructor to accept
GroupRepositoryPort, and use that port in all methods of ChangeGroupService
(same access pattern as FindGroupService/CreateGroupService/DeleteGroupService)
so the service no longer directly references GroupRepository.
- Line 7: ChangeGroupService currently depends on the concrete
GroupRoleRepositoryAdapter which violates dependency inversion; replace that
concrete dependency with the port interface GroupRoleRepositoryPort by updating
the import, field type, constructor parameter (and any usages) from
GroupRoleRepositoryAdapter to GroupRoleRepositoryPort so the service depends on
the interface (keep the adapter only wired in at composition/root). Ensure all
references inside ChangeGroupService (fields, method signatures, and any casts)
use GroupRoleRepositoryPort and remove direct adapter imports.

In `@src/main/java/flipnote/group/application/service/DeleteGroupService.java`:
- Around line 22-32: DeleteGroupService.deleteGroup is missing a pre-check that
the group exists before calling groupRoleRepository.checkRole, which can cause
NPEs or misleading "not owner" errors; fix by first querying
groupRepository.existsById(cmd.groupId()) or fetching the group (using the same
existence check pattern as ChangeGroupService), throw a clear NotFoundException
(or similar) if absent, and only then call
groupRoleRepository.checkRole(cmd.userId(), cmd.groupId(),
GroupMemberRole.OWNER) before proceeding to
groupRepository.delete(cmd.groupId()).

In `@src/main/java/flipnote/group/application/service/FindGroupService.java`:
- Around line 32-37: Replace the use of IllegalArgumentException for access
checks with a custom runtime exception (e.g., GroupAccessDeniedException) and
throw it from the membership/ownership checks (in FindGroupService where
groupMemberRepository.existsUserInGroup(cmd.groupId(), cmd.userId()) is used,
and likewise in ChangeGroupService and DeleteGroupService where the "not owner"
check occurs); add a simple GroupAccessDeniedException(String message) class and
register an `@ExceptionHandler` (or controller advice) that maps
GroupAccessDeniedException to HTTP 403 Forbidden so these access failures
produce 403 responses instead of 400.

In
`@src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRoleRepository.java`:
- Line 9: The repository method findByGroupIdAndRole currently returns a
RoleEntity that can be null causing NPE in GroupRoleRepositoryAdapter.checkRole;
change its signature to return Optional<RoleEntity> and update the adapter
(method checkRole) to map the Optional to a boolean by calling
groupMemberRepository.existsByUserIdAndGroupRoleId(userId, roleEntity.getId())
when present and returning false when empty. Ensure to update imports/usages of
findByGroupIdAndRole and adjust any tests or callers accordingly.
🧹 Nitpick comments (10)
src/main/java/flipnote/group/adapter/out/entity/PermissionEntity.java (1)

18-51: groupRoleId를 bare Long이 아닌 @ManyToOne 관계로 매핑하는 것을 고려해 보세요.

현재 groupRoleIdLong으로 직접 매핑하고 있어 JPA의 관계 탐색, cascade, orphan removal 등을 활용할 수 없습니다. RoleEntity에서도 동일한 패턴을 사용 중이므로 프로젝트 컨벤션으로 보이지만, 향후 권한 조회 시 join query가 복잡해질 수 있습니다.

src/main/java/flipnote/group/application/service/FindGroupService.java (1)

29-42: 읽기 전용 서비스 메서드에 @Transactional(readOnly = true) 추가를 고려하세요.

두 개의 repository 호출(existsUserInGroup + findById)이 하나의 트랜잭션 컨텍스트에서 실행되지 않아, 멤버 확인 후 그룹 조회 사이에 데이터 불일치가 발생할 수 있습니다. 다른 서비스(ChangeGroupService, CreateGroupService)에는 @Transactional이 적용되어 있어 일관성 측면에서도 권장합니다.

♻️ `@Transactional`(readOnly = true) 추가
 	`@Override`
+	`@Transactional`(readOnly = true)
 	public FindGroupResult findGroup(FindGroupCommand cmd) {
src/main/java/flipnote/group/application/service/CreateGroupService.java (1)

38-43: groupRoleRepository.create(groupId)의 반환값 의미가 명확하지 않습니다.

create(groupId)가 모든 기본 역할을 생성하고 OWNER 역할의 ID를 반환한다면, 메서드명이 이를 명확히 표현하지 못합니다. createDefaultRoles(groupId) 또는 반환 타입을 명시적으로 하는 것이 좋습니다.

♻️ 메서드명 개선 제안

포트 인터페이스와 어댑터에서:

-	Long create(Long groupId);
+	Long createDefaultRolesAndGetOwnerRoleId(Long groupId);

또는 좀 더 간결하게:

-	Long create(Long groupId);
+	Long createDefaultRoles(Long groupId);
src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java (1)

8-8: 파라미터 이름 id가 모호합니다.

Spring Data JPA 파생 쿼리 메서드에서 GroupRoleId를 매핑하므로 파라미터 이름도 groupRoleId로 일치시키는 것이 가독성에 좋습니다.

♻️ 제안
-	boolean existsByUserIdAndGroupRoleId(Long userId, Long id);
+	boolean existsByUserIdAndGroupRoleId(Long userId, Long groupRoleId);
src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java (1)

5-9: 메서드 이름과 반환값의 의미가 명확하지 않습니다.

  • create(Long groupId) → 그룹의 모든 기본 역할을 생성하고 OWNER의 roleId를 반환합니다. 메서드명과 반환값만으로는 이 동작을 추론하기 어렵습니다. createDefaultRoles 같은 이름이 더 의도를 잘 전달합니다.
  • checkRole → boolean 반환 메서드는 hasRole이 더 관용적입니다.
♻️ 제안
 public interface GroupRoleRepositoryPort {
-	Long create(Long groupId);
+	Long createDefaultRoles(Long groupId);
 
-	boolean checkRole(Long userId, Long groupId, GroupMemberRole groupMemberRole);
+	boolean hasRole(Long userId, Long groupId, GroupMemberRole groupMemberRole);
 }
src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java (2)

52-61: 인라인 전체 경로 참조 대신 import 문을 사용하세요.

Line 58에서 java.util.stream.Collectors를 인라인으로 사용하고 있습니다. 파일 상단에 import 문으로 선언하는 것이 일관성 있고 가독성이 좋습니다.

♻️ 제안

파일 상단 import 추가:

import java.util.stream.Collectors;
 			.collect(java.util.stream.Collectors.toMap(
+			.collect(Collectors.toMap(

27-42: OWNER, HEAD_MANAGER, MANAGER의 기본 권한이 동일합니다.

세 역할 모두 KICKJOIN_REQUEST_MANAGE 권한을 갖고 있어 역할 간 차별화가 없습니다. 의도된 것이라면 무시하셔도 되지만, 향후 역할별 권한을 다르게 설정할 계획이라면 TODO 주석을 남겨두는 것을 권장합니다.

src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java (1)

10-15: @Component가 불필요합니다.

모든 메서드가 static이고 생성자가 private이므로 Spring이 이 클래스를 빈으로 인스턴스화할 수 없습니다. @Component 어노테이션을 제거하는 것이 좋습니다.

♻️ 수정 제안
-@Component
 `@NoArgsConstructor`(access = AccessLevel.PRIVATE)
 public class GroupMemberMapper {
src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java (2)

53-58: Javadoc에 groupRoleId 파라미터 설명이 누락되어 있습니다.

@param groupRoleId가 빠져 있고, @return 설명도 비어 있습니다.

♻️ 수정 제안
 	/**
 	 * 멤버 생성
 	 * `@param` groupId
 	 * `@param` userId
+	 * `@param` groupRoleId
-	 * `@return`
+	 * `@return` GroupMemberEntity
 	 */

43-44: 의도적 설계 선택인 것 같으나, DB 레벨 FK 제약 조건 추가를 검토해보세요.

현재 코드는 @JoinColumn이나 @ManyToOne 없이 Long 타입으로 외래 키를 관리하고 있습니다. 같은 패턴이 RoleEntity(groupId), PermissionEntity(groupRoleId), GroupEntity(groupId) 등 전체 엔티티에 일관되게 적용되어 있어 의도적 설계로 보입니다.

다만 현재 설정에서는:

  • JPA 레벨의 FK 제약이 없고
  • DB 스키마 파일이 발견되지 않아 Hibernate auto-generated 스키마에도 명시적 FK 제약이 없을 가능성이 높습니다

애플리케이션 로직(예: groupRoleRepository.findByGroupIdAndRole())으로 참조 정합성을 검증하고 있으나, DB 레벨에서도 FK 제약을 정의하면 외부 시스템이나 직접 쿼리를 통한 데이터 불일치를 더욱 견고하게 방지할 수 있습니다. 느슨한 결합과 DB 무결성 중 어느 것을 우선시할지 팀과 함께 검토하는 것을 권장합니다.

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check GroupRepository usage in service layer files
rg "GroupRepository" --type=java -g '*/application/service/*' -n

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 219


🏁 Script executed:

#!/bin/bash
# Find and examine ChangeGroupService
fd "ChangeGroupService.java" --type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 143


🏁 Script executed:

#!/bin/bash
# Find GroupRepositoryPort to verify the abstraction exists
fd "GroupRepositoryPort" --type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Check the structure of application/service directory
fd . --type f -g "*/application/service/*.java" | head -20

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 186


🏁 Script executed:

#!/bin/bash
# List service files
ls -la src/main/java/flipnote/group/application/service/

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 472


🏁 Script executed:

#!/bin/bash
# Examine ChangeGroupService
cat -n src/main/java/flipnote/group/application/service/ChangeGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1893


🏁 Script executed:

#!/bin/bash
# Examine GroupRepositoryPort
cat -n src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 376


🏁 Script executed:

#!/bin/bash
# Check FindGroupService
cat -n src/main/java/flipnote/group/application/service/FindGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1667


🏁 Script executed:

#!/bin/bash
# Check CreateGroupService to verify the architectural pattern
cat -n src/main/java/flipnote/group/application/service/CreateGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1865


🏁 Script executed:

#!/bin/bash
# Check DeleteGroupService as well
cat -n src/main/java/flipnote/group/application/service/DeleteGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1385


GroupRepositoryPort를 통해 접근하도록 리팩토링이 필요합니다.

ChangeGroupService가 JPA 인터페이스인 GroupRepository를 직접 참조하고 있는데, FindGroupService, CreateGroupService, DeleteGroupService에서는 모두 GroupRepositoryPort를 통해 접근하고 있습니다. 아키텍처 일관성을 위해 동일한 패턴으로 통일해야 합니다.

🤖 Prompt for AI Agents
In `@src/main/java/flipnote/group/application/service/ChangeGroupService.java` at
line 20, ChangeGroupService currently depends on the JPA interface field
jpaGroupRepository (type GroupRepository); refactor it to depend on the
abstraction GroupRepositoryPort instead: replace the field jpaGroupRepository
with a GroupRepositoryPort-typed field, update the constructor to accept
GroupRepositoryPort, and use that port in all methods of ChangeGroupService
(same access pattern as FindGroupService/CreateGroupService/DeleteGroupService)
so the service no longer directly references GroupRepository.

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

🤖 Fix all issues with AI agents
In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java`:
- Around line 84-91: The checkRole method in GroupRoleRepositoryAdapter
currently throws an exception when groupRoleRepository.findByGroupIdAndRole(...)
is empty, which breaks the boolean-based control flow; change it so that if the
RoleEntity is not found the method returns false instead of throwing.
Concretely, locate checkRole and replace the orElseThrow flow with a safe lookup
that returns false when roleEntity is absent and only calls
groupMemberRepository.existsByUserIdAndGroupRoleId(userId, roleEntity.getId())
when present. Ensure the method signature and return semantics remain boolean
and no exception is thrown for a missing role.
- Around line 49-73: The create(Long groupId) method in
GroupRoleRepositoryAdapter performs multiple repository saves
(groupRoleRepository.save(...) and groupRolePermissionRepository.saveAll(...))
but lacks transactional protection; annotate the create method (or the
GroupRoleRepositoryAdapter class) with `@Transactional` (from
org.springframework.transaction.annotation.Transactional) so the role saves and
permission saves are executed in one transaction and will rollback together on
failure (optionally specify rollbackFor=Exception.class if desired); ensure
imports are added and existing callers are compatible with adding transactional
behavior.
🧹 Nitpick comments (2)
src/main/java/flipnote/group/application/service/ChangeGroupService.java (1)

37-43: 소유자 검증 로직 추가는 좋으나, 에러 처리 개선이 필요합니다.

IllegalArgumentException은 잘못된 인자에 대한 예외이므로, 권한 부족 상황에는 의미적으로 적합하지 않습니다. TODO 주석(Line 40)에서도 인지하고 계시므로, 전용 권한 예외(예: AccessDeniedException 또는 커스텀 예외)로 조기 전환을 권장합니다.

추가로, checkRole이 내부적으로 orElseThrow를 호출하므로, 존재하지 않는 역할에 대해 IllegalArgumentException("not role")이 먼저 던져질 수 있습니다. 의미 있는 에러 메시지 구분이 필요합니다.

권한 부족 전용 커스텀 예외 클래스를 생성해 드릴까요?

src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java (1)

52-61: 스트림 생성 간소화 및 import 정리

Arrays.stream(new GroupMemberRole[]{...})보다 Stream.of(...)가 간결하고, java.util.stream.Collectors는 fully qualified 대신 import 처리하는 것이 관례에 맞습니다.

♻️ 수정 제안

파일 상단 import 추가:

import java.util.stream.Collectors;
import java.util.stream.Stream;

메서드 내 코드:

-		Map<GroupMemberRole, Long> roleIdByRole = Arrays.stream(new GroupMemberRole[]{
-				GroupMemberRole.OWNER,
-				GroupMemberRole.HEAD_MANAGER,
-				GroupMemberRole.MANAGER,
-				GroupMemberRole.MEMBER
-			})
-			.collect(java.util.stream.Collectors.toMap(
+		Map<GroupMemberRole, Long> roleIdByRole = Stream.of(
+				GroupMemberRole.OWNER,
+				GroupMemberRole.HEAD_MANAGER,
+				GroupMemberRole.MANAGER,
+				GroupMemberRole.MEMBER
+			)
+			.collect(Collectors.toMap(
 				role -> role,
 				role -> groupRoleRepository.save(RoleEntity.create(groupId, role)).getId()
 			));

@stoneTiger0912 stoneTiger0912 merged commit 8dd91b7 into main Feb 18, 2026
1 check was pending
@stoneTiger0912 stoneTiger0912 deleted the feat/add-member-role branch February 18, 2026 14:46
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

Comments