From 1333989c3e236711113c13f1d2c0efad9a876eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Thu, 19 Feb 2026 14:14:41 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A1=B0=EC=A7=81=20?= =?UTF-8?q?=EB=A7=B4=EB=B2=84=20=EA=B6=8C=ED=95=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/request/OrgRequest.java | 8 ++++++++ .../application/mapper/OrgConverter.java | 12 +++++++++++- .../organization/exception/code/OrgErrorCode.java | 3 +++ .../organization/persistence/entity/OrgMember.java | 4 ++++ .../persistence/repository/OrgMemberRepository.java | 8 ++++++++ .../organization/presentation/OrgController.java | 11 +++++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java index c29ed56..6cb5d96 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java @@ -1,6 +1,9 @@ package com.whereyouad.WhereYouAd.domains.organization.application.dto.request; +import com.whereyouad.WhereYouAd.domains.organization.domain.constant.OrgRole; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; public class OrgRequest { @@ -22,4 +25,9 @@ public record Update ( String logoUrl ) {} + public record UpdateRole ( + @Schema(description = "조직 내 역할(ADMIN / MEMBER)", example = "ADMIN", allowableValues = {"ADMIN", "MEMBER"}) + @NotNull(message = "역할은 필수입니다.") + OrgRole orgRole + ) {} } diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java index f8dc294..bb06c64 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java @@ -39,6 +39,16 @@ public static Organization toOrganization(Long userId, OrgRequest.Create request .build(); } + // 단일 OrgMember -> OrgMemberDTO 변환 + public static OrgResponse.OrgMemberDTO toOrgMemberDTO(OrgMember orgMember) { + return new OrgResponse.OrgMemberDTO( + orgMember.getUser().getName(), + orgMember.getUser().getEmail(), + orgMember.getUser().getProfileImageUrl(), + orgMember.getRole().name() + ); + } + // 조직 멤버 Slice DTO 변환 (무한 스크롤) public static OrgResponse.OrgMemberSliceDTO toOrgMemberSliceDTO( boolean hasNext, @@ -60,4 +70,4 @@ public static OrgResponse.OrgMemberSliceDTO toOrgMemberSliceDTO( memberDTOs ); } -} +} \ No newline at end of file diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java index 1204834..7d42392 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java @@ -10,12 +10,15 @@ public enum OrgErrorCode implements BaseErrorCode { //400 ORG_NAME_DUPLICATE(HttpStatus.BAD_REQUEST, "ORG_400_1", "사용자가 이미 속해있는 조직의 이름입니다."), + ORG_CANNOT_ADMIN_TO_MEMBER(HttpStatus.BAD_REQUEST, "ORG_400_2", "ADMIN은 MEMBER로 변경할 수 없습니다."), //403 ORG_FORBIDDEN(HttpStatus.FORBIDDEN, "ORG_403_1", "해당 요청은 조직 생성자만 요청 가능합니다."), + ORG_MEMBER_FORBIDDEN(HttpStatus.FORBIDDEN, "ORG_403_2", "해당 요청은 ADMIN 권한을 가진 멤버만 요청 가능합니다."), //404 ORG_NOT_FOUND(HttpStatus.NOT_FOUND, "ORG_404_1", "해당 id 의 조직이 존재하지 않습니다."), + ORG_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORG_404_2", "해당 조직에 속한 멤버가 존재하지 않습니다."), //409 ORG_ALREADY_ACTIVE(HttpStatus.CONFLICT, "ORG_409_1", "해당 조직은 이미 활성화 상태 입니다.") diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/entity/OrgMember.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/entity/OrgMember.java index cee7b65..b023a18 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/entity/OrgMember.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/entity/OrgMember.java @@ -34,4 +34,8 @@ public class OrgMember { //중간 테이블이므로 BaseEntity 미적용 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "org_id") private Organization organization; + + public void updateRole(OrgRole role) { + this.role = role; + } } diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java index 5324f89..2394b60 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java @@ -11,6 +11,7 @@ import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface OrgMemberRepository extends JpaRepository { @@ -35,6 +36,13 @@ Slice findByOrganizationIdWithCursor( Pageable pageable ); + // userId 와 orgId 로 특정 OrgMember 조회 + @Query("SELECT om FROM OrgMember om " + + "WHERE om.user.id = :userId " + + "AND om.organization.id = :orgId " + + "AND om.user.status = 'ACTIVE'") + Optional findByUserIdAndOrgId(@Param("userId") Long userId, @Param("orgId") Long orgId); + // 조직의 전체 멤버 수 조회 @Query("SELECT COUNT(m) FROM OrgMember m " + "JOIN m.user u " + diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java index eeeec47..f516b96 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java @@ -105,4 +105,15 @@ public ResponseEntity> getOrgMembers OrgResponse.OrgMemberCountDTO response = orgQueryService.getOrgMembersCount(orgId); return ResponseEntity.ok(DataResponse.from(response)); } + + @PatchMapping("/members/{orgId}/{memberId}") + public ResponseEntity> updateOrgMembersRole( + @AuthenticationPrincipal(expression = "userId") Long userId, + @PathVariable Long orgId, + @PathVariable Long memberId, + @RequestBody OrgRequest.UpdateRole dto + ) { + OrgResponse.OrgMemberDTO response = orgService.updateOrgMembersRole(userId, orgId, memberId, dto); + return ResponseEntity.ok(DataResponse.from(response)); + } } \ No newline at end of file From 4cb4ebc70ce9a484b6b5e40bab445faf8123605f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Thu, 19 Feb 2026 14:14:50 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A1=B0=EC=A7=81=20?= =?UTF-8?q?=EB=A7=B4=EB=B2=84=20=EA=B6=8C=ED=95=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?API=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/service/OrgService.java | 3 ++ .../domain/service/OrgServiceImpl.java | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java index 052c1d1..ee4ca32 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java @@ -16,4 +16,7 @@ public interface OrgService { void removeOrganizationSoft(Long userId, Long orgId); OrgResponse.Delete restoreOrganization(Long userId, Long orgId); + + // 조직 내 멤버 권한 변경 메서드 + OrgResponse.OrgMemberDTO updateOrgMembersRole(Long userId, Long orgId, Long memberId, OrgRequest.UpdateRole dto); } diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java index e6db79b..a291235 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java @@ -4,6 +4,7 @@ import com.whereyouad.WhereYouAd.domains.organization.application.dto.response.OrgResponse; import com.whereyouad.WhereYouAd.domains.organization.application.mapper.OrgConverter; import com.whereyouad.WhereYouAd.domains.organization.application.mapper.OrgMemberConverter; +import com.whereyouad.WhereYouAd.domains.organization.domain.constant.OrgRole; import com.whereyouad.WhereYouAd.domains.organization.domain.constant.OrgStatus; import com.whereyouad.WhereYouAd.domains.organization.exception.code.OrgErrorCode; import com.whereyouad.WhereYouAd.domains.organization.exception.handler.OrgHandler; @@ -132,4 +133,34 @@ public void removeOrganizationSoft(Long userId, Long orgId) { //조직 status 만 DELETED 로 변경 후 종료 organization.softDelete(); } + + public OrgResponse.OrgMemberDTO updateOrgMembersRole(Long userId, Long orgId, Long memberId, + OrgRequest.UpdateRole dto) { + + // 1. 조직 존재 여부 확인 + Organization organization = orgRepository.findById(orgId) + .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_NOT_FOUND)); + + // 2. 요청자가 해당 조직의 ADMIN인지 확인 + OrgMember requester = orgMemberRepository.findByUserIdAndOrgId(userId, orgId) + .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND)); + + if (requester.getRole() != OrgRole.ADMIN) { + throw new OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN); + } + + // 3. 해당 조직 내 멤버 조회 (상대방도 ADMIN이라면 MEMBER로 변경 불가) + OrgMember orgMember = orgMemberRepository.findByUserIdAndOrgId(memberId, orgId) + .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND)); + + if (requester.getRole() == OrgRole.ADMIN) { + throw new OrgHandler(OrgErrorCode.ORG_CANNOT_ADMIN_TO_MEMBER); + } + + // 역할 변경 (더티체킹) + orgMember.updateRole(dto.orgRole()); + + // 변경된 멤버 정보를 DTO 로 반환 + return OrgConverter.toOrgMemberDTO(orgMember); + } } From c1c35e08c67de6615e6ae7caf905cbebd8d21036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Thu, 19 Feb 2026 14:15:18 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=93=9D=20docs:=20OrgControllerDocs=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/docs/OrgControllerDocs.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java index 1e7fca9..7450d09 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java @@ -96,4 +96,21 @@ ResponseEntity> getOrgMembers( ResponseEntity> getOrgMembersCount( @PathVariable Long orgId ); + + @Operation( + summary = "조직 맴버 권한 변경 API", + description = "맴버 권한 변경을 요청한 유저의 권한이 ADMIN인 경우 실행이 가능합니다. memberId에 해당하는 맴버의 권한을 변경시킵니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 수)"), + @ApiResponse(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"), + @ApiResponse(responseCode = "403", description = "권한이 부족한 경우(요청을 보낸 유저의 권한이 ADMIN이 아닌 경우)"), + @ApiResponse(responseCode = "404", description = "해당 id의 데이터 존재 X") + }) + ResponseEntity> updateOrgMembersRole( + @AuthenticationPrincipal(expression = "userId") Long userId, + @PathVariable Long orgId, + @PathVariable Long memberId, + @RequestBody OrgRequest.UpdateRole dto + ); } \ No newline at end of file From 5233625ee3a96ccabfe4557d8386674f77aac640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Thu, 19 Feb 2026 14:16:38 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Converter?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=20=EC=9D=B4=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organization/application/mapper/OrgConverter.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java index bb06c64..9278b3b 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/mapper/OrgConverter.java @@ -56,12 +56,7 @@ public static OrgResponse.OrgMemberSliceDTO toOrgMemberSliceDTO( List orgMembers ) { List memberDTOs = orgMembers.stream() - .map(m -> new OrgResponse.OrgMemberDTO( - m.getUser().getName(), - m.getUser().getEmail(), - m.getUser().getProfileImageUrl(), - m.getRole().name() - )) + .map(OrgConverter::toOrgMemberDTO) .toList(); return new OrgResponse.OrgMemberSliceDTO( From 9fffcdcc1778fde7a48071c32a0232e904727e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Thu, 19 Feb 2026 18:28:15 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=EC=9D=98=20=EA=B6=8C=ED=95=9C=EC=9D=84=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=ED=95=A0=20=EB=95=8C=20=EC=9E=98=EB=AA=BB=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=ED=95=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/organization/domain/service/OrgServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java index 2951a7d..bcd702a 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java @@ -210,7 +210,7 @@ public OrgResponse.OrgMemberDTO updateOrgMembersRole(Long userId, Long orgId, Lo OrgMember orgMember = orgMemberRepository.findByUserIdAndOrgId(memberId, orgId) .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND)); - if (requester.getRole() == OrgRole.ADMIN) { + if (orgMember.getRole() == OrgRole.ADMIN) { throw new OrgHandler(OrgErrorCode.ORG_CANNOT_ADMIN_TO_MEMBER); } From d8425d04f864e77e42b8fb6fbd3457f9e5038d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Sun, 22 Feb 2026 00:21:05 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=E2=9C=A8=20feat:=20@Vaild=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/organization/presentation/OrgController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java index d891272..70488d8 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java @@ -131,7 +131,7 @@ public ResponseEntity> updateOrgMembersRo @AuthenticationPrincipal(expression = "userId") Long userId, @PathVariable Long orgId, @PathVariable Long memberId, - @RequestBody OrgRequest.UpdateRole dto + @RequestBody @Valid OrgRequest.UpdateRole dto ) { OrgResponse.OrgMemberDTO response = orgService.updateOrgMembersRole(userId, orgId, memberId, dto); return ResponseEntity.ok(DataResponse.from(response)); From 39b1f092326498fc10ea3408d25d83d75d3b0461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Sun, 22 Feb 2026 01:03:05 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20ADMIN=20?= =?UTF-8?q?=EA=B0=95=EB=93=B1=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD(AD?= =?UTF-8?q?MIN=EC=9D=80=202=EB=AA=85=20=EC=9D=B4=EC=83=81=EC=9D=BC=20?= =?UTF-8?q?=EB=95=8C=EB=A7=8C=20=EA=B0=95=EB=93=B1=20=EA=B0=80=EB=8A=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../organization/domain/service/OrgServiceImpl.java | 12 +++++++++--- .../organization/exception/code/OrgErrorCode.java | 13 ++++--------- .../persistence/repository/OrgMemberRepository.java | 4 ++++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java index 27071a2..2752acc 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java @@ -212,12 +212,18 @@ public OrgResponse.OrgMemberDTO updateOrgMembersRole(Long userId, Long orgId, Lo throw new OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN); } - // 3. 해당 조직 내 멤버 조회 (상대방도 ADMIN이라면 MEMBER로 변경 불가) + // 3. 권한 변경 대상 멤버 조회 OrgMember orgMember = orgMemberRepository.findByUserIdAndOrgId(memberId, orgId) .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND)); - if (orgMember.getRole() == OrgRole.ADMIN) { - throw new OrgHandler(OrgErrorCode.ORG_CANNOT_ADMIN_TO_MEMBER); + // 4. ADMIN -> MEMBER 강등 시: 조직 내 ADMIN이 2명 이상이어야만 허용 + // 해당 멤버 ADMIN, 요청 역할 MEMBER인 경우 + boolean isDemoting = orgMember.getRole() == OrgRole.ADMIN && dto.orgRole() == OrgRole.MEMBER; + if (isDemoting) { + long adminCount = orgMemberRepository.countByOrganizationIdAndRole(orgId, OrgRole.ADMIN); + if (adminCount <= 2) { + throw new OrgHandler(OrgErrorCode.ORG_LAST_ADMIN); + } } // 역할 변경 (더티체킹) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java index 53ae4db..a92d06f 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java @@ -12,11 +12,13 @@ public enum OrgErrorCode implements BaseErrorCode { ORG_NAME_DUPLICATE(HttpStatus.BAD_REQUEST, "ORG_400_1", "사용자가 이미 속해있는 조직의 이름입니다."), ORG_CANNOT_KICK_SELF(HttpStatus.BAD_REQUEST, "ORG_400_2", "자기 자신을 추방할 수 없습니다."), ORG_CANNOT_KICK_ADMIN(HttpStatus.BAD_REQUEST, "ORG_400_3", "ADMIN은 추방할 수 없습니다."), - ORG_CANNOT_ADMIN_TO_MEMBER(HttpStatus.BAD_REQUEST, "ORG_400_4", "ADMIN은 MEMBER로 변경할 수 없습니다."), + ORG_CANNOT_ROLE_CHANGE_SELF(HttpStatus.BAD_REQUEST, "ORG_400_4", "본인의 역할은 변경할 수 없습니다."), + ORG_LAST_ADMIN(HttpStatus.BAD_REQUEST, "ORG_400_5", "ADMIN은 2명 이상입니다."), // 403 ORG_FORBIDDEN(HttpStatus.FORBIDDEN, "ORG_403_1", "해당 요청은 조직 생성자만 요청 가능합니다."), ORG_MEMBER_FORBIDDEN(HttpStatus.FORBIDDEN, "ORG_403_2", "해당 요청은 ADMIN 권한을 가진 멤버만 요청 가능합니다."), + ORG_INVITATION_FORBIDDEN_USER(HttpStatus.FORBIDDEN, "ORG_INVITATION_403_1", "초대된 이메일과 현재 로그인한 사용자의 이메일이 일치하지 않습니다."), // 404 ORG_NOT_FOUND(HttpStatus.NOT_FOUND, "ORG_404_1", "해당 id 의 조직이 존재하지 않습니다."), @@ -24,18 +26,11 @@ public enum OrgErrorCode implements BaseErrorCode { //409 ORG_ALREADY_ACTIVE(HttpStatus.CONFLICT, "ORG_409_1", "해당 조직은 이미 활성화 상태 입니다."), + ORG_MEMBER_ALREADY_ACTIVE(HttpStatus.CONFLICT, "ORG_MEMBER_409_1", "이미 해당 조직에 초대되어있습니다."), //410 ORG_SOFT_DELETED(HttpStatus.GONE, "ORG_410_1", "해당 조직은 삭제된 조직입니다.(Soft Delete)"), - - // 409 - ORG_MEMBER_ALREADY_ACTIVE(HttpStatus.CONFLICT, "ORG_MEMBER_409_1", "이미 해당 조직에 초대되어있습니다."), - - // 400 ORG_INVITATION_INVALID(HttpStatus.BAD_REQUEST, "ORG_INVITATION_400", "조직 초대 토큰이 만료되었거나 유효하지 않습니다."), - - // 403 - ORG_INVITATION_FORBIDDEN_USER(HttpStatus.FORBIDDEN, "ORG_INVITATION_403_1", "초대된 이메일과 현재 로그인한 사용자의 이메일이 일치하지 않습니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java index 62fb2b8..6cad323 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java @@ -1,5 +1,6 @@ package com.whereyouad.WhereYouAd.domains.organization.persistence.repository; +import com.whereyouad.WhereYouAd.domains.organization.domain.constant.OrgRole; import com.whereyouad.WhereYouAd.domains.organization.persistence.entity.OrgMember; import com.whereyouad.WhereYouAd.domains.organization.persistence.entity.Organization; import com.whereyouad.WhereYouAd.domains.user.domain.constant.UserStatus; @@ -58,4 +59,7 @@ int countByOrganizationIdAndUserStatus( ); Boolean existsByUserAndOrganization(User user, Organization organization); + + // 조직 id에 해당하는 역할 인원 수 조회 + long countByOrganizationIdAndRole(Long orgId, OrgRole orgRole); } \ No newline at end of file From 64d91d242f894360cc0ecc6a68b3c989bfd536dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Sun, 22 Feb 2026 01:03:31 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=B3=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=97=AD=ED=95=A0=EC=9D=80=20=EB=B3=80=EA=B2=BD=20=EB=B6=88?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/organization/domain/service/OrgServiceImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java index 2752acc..494f2ce 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java @@ -200,6 +200,11 @@ public void removeMemberFromOrg(Long userId, Long orgId, Long memberId) { public OrgResponse.OrgMemberDTO updateOrgMembersRole(Long userId, Long orgId, Long memberId, OrgRequest.UpdateRole dto) { + // 0. 본인 권한 변경 불가 + if (Objects.equals(userId, memberId)) { + throw new OrgHandler(OrgErrorCode.ORG_CANNOT_ROLE_CHANGE_SELF); + } + // 1. 조직 존재 여부 확인 Organization organization = orgRepository.findById(orgId) .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_NOT_FOUND)); From 86c8bb23b1b31ec6f89a6d38aae7fc70bc14e5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EA=B7=9C?= Date: Sun, 22 Feb 2026 01:11:51 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20ADMIN?= =?UTF-8?q?=EC=9D=80=202=EB=AA=85=20=EC=9D=B4=EC=83=81=EC=9D=BC=20?= =?UTF-8?q?=EB=95=8C=EB=A7=8C=20=EA=B0=95=EB=93=B1=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/organization/domain/service/OrgServiceImpl.java | 2 +- .../domains/organization/exception/code/OrgErrorCode.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java index 494f2ce..b211711 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java @@ -226,7 +226,7 @@ public OrgResponse.OrgMemberDTO updateOrgMembersRole(Long userId, Long orgId, Lo boolean isDemoting = orgMember.getRole() == OrgRole.ADMIN && dto.orgRole() == OrgRole.MEMBER; if (isDemoting) { long adminCount = orgMemberRepository.countByOrganizationIdAndRole(orgId, OrgRole.ADMIN); - if (adminCount <= 2) { + if (adminCount < 2) { throw new OrgHandler(OrgErrorCode.ORG_LAST_ADMIN); } } diff --git a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java index a92d06f..f9d4078 100644 --- a/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java +++ b/src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java @@ -13,7 +13,7 @@ public enum OrgErrorCode implements BaseErrorCode { ORG_CANNOT_KICK_SELF(HttpStatus.BAD_REQUEST, "ORG_400_2", "자기 자신을 추방할 수 없습니다."), ORG_CANNOT_KICK_ADMIN(HttpStatus.BAD_REQUEST, "ORG_400_3", "ADMIN은 추방할 수 없습니다."), ORG_CANNOT_ROLE_CHANGE_SELF(HttpStatus.BAD_REQUEST, "ORG_400_4", "본인의 역할은 변경할 수 없습니다."), - ORG_LAST_ADMIN(HttpStatus.BAD_REQUEST, "ORG_400_5", "ADMIN은 2명 이상입니다."), + ORG_LAST_ADMIN(HttpStatus.BAD_REQUEST, "ORG_400_5", "마지막 ADMIN은 강등할 수 없습니다."), // 403 ORG_FORBIDDEN(HttpStatus.FORBIDDEN, "ORG_403_1", "해당 요청은 조직 생성자만 요청 가능합니다."),