From 66b5e4a28d4f11d0a6906ac9d9db5e411c206bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 25 Feb 2026 23:16:04 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feat:=20=ED=8A=B9=EC=A0=95=20=EA=B7=B8?= =?UTF-8?q?=EB=A3=B9=EC=9D=98=20=EB=A9=A4=EB=B2=84=20=EC=B6=94=EB=B0=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/MemberController.java | 23 +++++++++++++-- .../adapter/in/web/PermissionController.java | 4 --- .../GroupMemberRepositoryAdapter.java | 5 ++++ .../GroupRoleRepositoryAdapter.java | 7 ++--- .../port/in/KickMemberUseCase.java | 7 +++++ .../port/in/command/KickMemberCommand.java | 8 +++++ .../port/out/GroupMemberRepositoryPort.java | 2 ++ .../service/KickMemberService.java | 29 +++++++++++++++++++ .../model/permission/GroupPermission.java | 2 +- 9 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 src/main/java/flipnote/group/application/port/in/KickMemberUseCase.java create mode 100644 src/main/java/flipnote/group/application/port/in/command/KickMemberCommand.java create mode 100644 src/main/java/flipnote/group/application/service/KickMemberService.java diff --git a/src/main/java/flipnote/group/adapter/in/web/MemberController.java b/src/main/java/flipnote/group/adapter/in/web/MemberController.java index 2c0573f..5f1e600 100644 --- a/src/main/java/flipnote/group/adapter/in/web/MemberController.java +++ b/src/main/java/flipnote/group/adapter/in/web/MemberController.java @@ -12,16 +12,19 @@ import flipnote.group.api.dto.response.FindGroupMemberResponseDto; import flipnote.group.application.port.in.FindGroupMemberUseCase; +import flipnote.group.application.port.in.KickMemberUseCase; import flipnote.group.application.port.in.command.FindGroupMemberCommand; +import flipnote.group.application.port.in.command.KickMemberCommand; import flipnote.group.application.port.in.result.FindGroupMemberResult; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @RestController -@RequestMapping("/v1/groups/{groupId}") +@RequestMapping("/v1/groups/{groupId}/members") public class MemberController { private final FindGroupMemberUseCase findGroupMemberUseCase; + private final KickMemberUseCase kickMemberUseCase; /** * 그룹 내 멤버 전체 조회 @@ -29,7 +32,7 @@ public class MemberController { * @param groupId * @return */ - @GetMapping("/members") + @GetMapping("") public ResponseEntity findGroupMembers( @RequestHeader("X-USER-ID") Long userId, @PathVariable("groupId") Long groupId) { @@ -43,4 +46,20 @@ public ResponseEntity findGroupMembers( return ResponseEntity.ok(res); } + + //todo 그룹 멤버 추방 + @DeleteMapping("/{memberId}") + public ResponseEntity kickGroupMember( + @RequestHeader("X-USER-ID") Long userId, + @PathVariable("groupId") Long groupId, + @PathVariable("memberId") Long memberId + ) { + KickMemberCommand cmd = new KickMemberCommand(userId, groupId, memberId); + + kickMemberUseCase.kickMember(cmd); + + return ResponseEntity.noContent().build(); + } + + //todo 가입 신청 허가 } diff --git a/src/main/java/flipnote/group/adapter/in/web/PermissionController.java b/src/main/java/flipnote/group/adapter/in/web/PermissionController.java index 40c693f..cb9b0ae 100644 --- a/src/main/java/flipnote/group/adapter/in/web/PermissionController.java +++ b/src/main/java/flipnote/group/adapter/in/web/PermissionController.java @@ -94,8 +94,4 @@ public ResponseEntity findMyPermission( return ResponseEntity.ok(res); } - - //todo 그룹 멤버 추방 - - //todo 가입 신청 허가 } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java index fc21c1b..bc65a69 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java @@ -85,4 +85,9 @@ public GroupMember findMyRole(Long groupId, Long userId) { return GroupMemberMapper.toDomain(entity); } + + @Override + public void deleteGroupMember(Long memberId) { + groupMemberRepository.deleteById(memberId); + } } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java index 5a3d6ce..f47761e 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java @@ -10,7 +10,6 @@ import flipnote.group.adapter.out.entity.PermissionEntity; import flipnote.group.adapter.out.entity.RoleEntity; import flipnote.group.application.port.out.GroupRoleRepositoryPort; -import flipnote.group.domain.model.member.GroupMember; import flipnote.group.domain.model.member.GroupMemberRole; import flipnote.group.domain.model.permission.GroupPermission; import flipnote.group.infrastructure.persistence.jpa.GroupMemberRepository; @@ -29,15 +28,15 @@ public class GroupRoleRepositoryAdapter implements GroupRoleRepositoryPort { private static final Map> DEFAULT_PERMS_BY_ROLE = Map.of( GroupMemberRole.OWNER, List.of( - GroupPermission.KICK, + GroupPermission.MEMBER_MANAGE, GroupPermission.JOIN_REQUEST_MANAGE ), GroupMemberRole.HEAD_MANAGER, List.of( - GroupPermission.KICK, + GroupPermission.MEMBER_MANAGE, GroupPermission.JOIN_REQUEST_MANAGE ), GroupMemberRole.MANAGER, List.of( - GroupPermission.KICK, + GroupPermission.MEMBER_MANAGE, GroupPermission.JOIN_REQUEST_MANAGE ), GroupMemberRole.MEMBER, List.of() diff --git a/src/main/java/flipnote/group/application/port/in/KickMemberUseCase.java b/src/main/java/flipnote/group/application/port/in/KickMemberUseCase.java new file mode 100644 index 0000000..8f4eb27 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/KickMemberUseCase.java @@ -0,0 +1,7 @@ +package flipnote.group.application.port.in; + +import flipnote.group.application.port.in.command.KickMemberCommand; + +public interface KickMemberUseCase { + void kickMember(KickMemberCommand cmd); +} diff --git a/src/main/java/flipnote/group/application/port/in/command/KickMemberCommand.java b/src/main/java/flipnote/group/application/port/in/command/KickMemberCommand.java new file mode 100644 index 0000000..becdda8 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/command/KickMemberCommand.java @@ -0,0 +1,8 @@ +package flipnote.group.application.port.in.command; + +public record KickMemberCommand( + Long userId, + Long groupId, + Long memberId +) { +} diff --git a/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java index 6e66f4f..f1f8496 100644 --- a/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java +++ b/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java @@ -15,4 +15,6 @@ public interface GroupMemberRepositoryPort { List findMemberInfo(Long groupId); GroupMember findMyRole(Long groupId, Long userId); + + void deleteGroupMember(Long memberId); } diff --git a/src/main/java/flipnote/group/application/service/KickMemberService.java b/src/main/java/flipnote/group/application/service/KickMemberService.java new file mode 100644 index 0000000..ebce5dd --- /dev/null +++ b/src/main/java/flipnote/group/application/service/KickMemberService.java @@ -0,0 +1,29 @@ +package flipnote.group.application.service; + +import org.springframework.stereotype.Service; + +import flipnote.group.application.port.in.KickMemberUseCase; +import flipnote.group.application.port.in.command.KickMemberCommand; +import flipnote.group.application.port.out.GroupMemberRepositoryPort; +import flipnote.group.application.port.out.GroupRoleRepositoryPort; +import flipnote.group.domain.model.permission.GroupPermission; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class KickMemberService implements KickMemberUseCase { + + private final GroupMemberRepositoryPort groupMemberRepository; + private final GroupRoleRepositoryPort groupRoleRepository; + + @Override + public void kickMember(KickMemberCommand cmd) { + //권한 체크 + boolean hasPermission = groupRoleRepository.checkPermission(cmd.userId(), cmd.groupId(), GroupPermission.MEMBER_MANAGE); + if(!hasPermission) { + throw new IllegalArgumentException("not exist permission"); + } + + groupMemberRepository.deleteGroupMember(cmd.memberId()); + } +} diff --git a/src/main/java/flipnote/group/domain/model/permission/GroupPermission.java b/src/main/java/flipnote/group/domain/model/permission/GroupPermission.java index 0a68e81..bba16ae 100644 --- a/src/main/java/flipnote/group/domain/model/permission/GroupPermission.java +++ b/src/main/java/flipnote/group/domain/model/permission/GroupPermission.java @@ -1,5 +1,5 @@ package flipnote.group.domain.model.permission; public enum GroupPermission { - KICK, JOIN_REQUEST_MANAGE + MEMBER_MANAGE, JOIN_REQUEST_MANAGE } From faab8eff409e4bbf7aad02532f85a0d9d98eee7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 25 Feb 2026 23:16:39 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Chore:=20todo=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flipnote/group/adapter/in/web/MemberController.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/flipnote/group/adapter/in/web/MemberController.java b/src/main/java/flipnote/group/adapter/in/web/MemberController.java index 5f1e600..e8fa265 100644 --- a/src/main/java/flipnote/group/adapter/in/web/MemberController.java +++ b/src/main/java/flipnote/group/adapter/in/web/MemberController.java @@ -46,8 +46,13 @@ public ResponseEntity findGroupMembers( return ResponseEntity.ok(res); } - - //todo 그룹 멤버 추방 + /** + * 그룹 멤버 추방 + * @param userId + * @param groupId + * @param memberId + * @return + */ @DeleteMapping("/{memberId}") public ResponseEntity kickGroupMember( @RequestHeader("X-USER-ID") Long userId, From 56515836af1946b29750ba2e53cf017873f6f771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 25 Feb 2026 23:17:44 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Chore:=20todo=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/flipnote/group/adapter/in/web/MemberController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/flipnote/group/adapter/in/web/MemberController.java b/src/main/java/flipnote/group/adapter/in/web/MemberController.java index e8fa265..f0e0633 100644 --- a/src/main/java/flipnote/group/adapter/in/web/MemberController.java +++ b/src/main/java/flipnote/group/adapter/in/web/MemberController.java @@ -65,6 +65,4 @@ public ResponseEntity kickGroupMember( return ResponseEntity.noContent().build(); } - - //todo 가입 신청 허가 } From 687be9a68411c9a3eb57095da04566d50b30360a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Sun, 1 Mar 2026 21:16:18 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Merge:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/adapter/out/entity/GroupMemberEntity.java | 4 +++- .../out/persistence/GroupMemberRepositoryAdapter.java | 5 +++++ .../out/persistence/GroupRoleRepositoryAdapter.java | 7 ++++++- .../group/application/service/KickMemberService.java | 2 ++ .../flipnote/group/domain/model/member/MemberInfo.java | 4 +++- .../persistence/jpa/GroupRoleRepository.java | 2 ++ 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java index 80836d7..17bb04e 100644 --- a/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java @@ -49,7 +49,8 @@ public class GroupMemberEntity extends BaseEntity { private RoleEntity role; @Builder - private GroupMemberEntity(Long groupId, Long userId, RoleEntity role) { + private GroupMemberEntity(Long id, Long groupId, Long userId, RoleEntity role) { + this.id = id; this.groupId = groupId; this.userId = userId; this.role = role; @@ -71,6 +72,7 @@ public static GroupMemberEntity create(Long groupId, Long userId, RoleEntity rol public MemberInfo toMemberInfo() { return MemberInfo.builder() + .memberId(this.getId()) .userId(this.getUserId()) .role(this.getRole().getRole()) .build(); diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java index 9104ca7..cbd069d 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java @@ -89,6 +89,11 @@ public GroupMemberEntity findMyRole(Long groupId, Long userId) { @Override public void deleteGroupMember(Long memberId) { + + groupMemberRepository.findById(memberId).orElseThrow( + () -> new IllegalArgumentException("not exist member") + ); + groupMemberRepository.deleteById(memberId); } } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java index ddf2425..59d38c0 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupRoleRepositoryAdapter.java @@ -104,7 +104,12 @@ public boolean checkRole(Long userId, Long groupId, GroupMemberRole groupMemberR */ @Override public boolean checkPermission(Long userId, Long groupId, GroupPermission permission) { - return groupRolePermissionRepository.existsUserInGroupPermission(groupId, userId, permission); + + GroupMemberEntity groupMember = groupMemberRepository.findByGroupIdAndUserId(groupId, userId).orElseThrow( + () -> new IllegalArgumentException("not exist member") + ); + + return groupRoleRepository.existsByGroupIdAndRole(groupId, groupMember.getRole().getRole()); } /** diff --git a/src/main/java/flipnote/group/application/service/KickMemberService.java b/src/main/java/flipnote/group/application/service/KickMemberService.java index ebce5dd..7640ae5 100644 --- a/src/main/java/flipnote/group/application/service/KickMemberService.java +++ b/src/main/java/flipnote/group/application/service/KickMemberService.java @@ -1,6 +1,7 @@ package flipnote.group.application.service; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import flipnote.group.application.port.in.KickMemberUseCase; import flipnote.group.application.port.in.command.KickMemberCommand; @@ -17,6 +18,7 @@ public class KickMemberService implements KickMemberUseCase { private final GroupRoleRepositoryPort groupRoleRepository; @Override + @Transactional public void kickMember(KickMemberCommand cmd) { //권한 체크 boolean hasPermission = groupRoleRepository.checkPermission(cmd.userId(), cmd.groupId(), GroupPermission.MEMBER_MANAGE); diff --git a/src/main/java/flipnote/group/domain/model/member/MemberInfo.java b/src/main/java/flipnote/group/domain/model/member/MemberInfo.java index ddc7390..07d220e 100644 --- a/src/main/java/flipnote/group/domain/model/member/MemberInfo.java +++ b/src/main/java/flipnote/group/domain/model/member/MemberInfo.java @@ -8,11 +8,13 @@ @Getter @NoArgsConstructor(access = AccessLevel.PRIVATE) public class MemberInfo { + private Long memberId; private Long userId; private GroupMemberRole role; @Builder - private MemberInfo(Long userId, GroupMemberRole role) { + private MemberInfo(Long memberId, Long userId, GroupMemberRole role) { + this.memberId = memberId; this.userId = userId; this.role = role; } diff --git a/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRoleRepository.java b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRoleRepository.java index 1996b3b..c1d9dfc 100644 --- a/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRoleRepository.java +++ b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRoleRepository.java @@ -10,4 +10,6 @@ public interface GroupRoleRepository extends JpaRepository { Optional findByGroupIdAndRole(Long groupId, GroupMemberRole groupMemberRole); + + boolean existsByGroupIdAndRole(Long groupId, GroupMemberRole role); } From e11cba2b3763515e73de64800629611010e7253f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Sun, 1 Mar 2026 21:28:55 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Fix:=20=EA=B6=8C=ED=95=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupMemberRepositoryAdapter.java | 16 +++++++++++++++- .../port/out/GroupMemberRepositoryPort.java | 2 ++ .../application/service/KickMemberService.java | 7 +++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java index cbd069d..2f469f6 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java @@ -1,6 +1,7 @@ package flipnote.group.adapter.out.persistence; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.springframework.stereotype.Repository; @@ -90,10 +91,23 @@ public GroupMemberEntity findMyRole(Long groupId, Long userId) { @Override public void deleteGroupMember(Long memberId) { - groupMemberRepository.findById(memberId).orElseThrow( + GroupMemberEntity groupMember = groupMemberRepository.findById(memberId).orElseThrow( () -> new IllegalArgumentException("not exist member") ); groupMemberRepository.deleteById(memberId); + + //그룹 인원수 동기화 + GroupEntity group = groupRepository.findByIdForUpdate(groupMember.getGroupId()).orElseThrow( + () -> new IllegalArgumentException("not exist group") + ); + + group.minusCount(); + + } + + @Override + public boolean checkMember(Long memberId) { + return groupMemberRepository.existsById(memberId); } } diff --git a/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java index 5ebf34e..c301161 100644 --- a/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java +++ b/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java @@ -17,4 +17,6 @@ public interface GroupMemberRepositoryPort { boolean checkOwner(Long groupId, Long userId); void deleteGroupMember(Long memberId); + + boolean checkMember(Long aLong); } diff --git a/src/main/java/flipnote/group/application/service/KickMemberService.java b/src/main/java/flipnote/group/application/service/KickMemberService.java index 7640ae5..3eb446c 100644 --- a/src/main/java/flipnote/group/application/service/KickMemberService.java +++ b/src/main/java/flipnote/group/application/service/KickMemberService.java @@ -26,6 +26,13 @@ public void kickMember(KickMemberCommand cmd) { throw new IllegalArgumentException("not exist permission"); } + boolean isExist = groupMemberRepository.checkMember(cmd.memberId()); + + if(!isExist) { + throw new IllegalArgumentException("not exist member"); + } + groupMemberRepository.deleteGroupMember(cmd.memberId()); + } }