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 4b47751..88c918b 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 @@ -66,6 +66,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, @@ -73,12 +83,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( @@ -87,4 +92,4 @@ public static OrgResponse.OrgMemberSliceDTO toOrgMemberSliceDTO( memberDTOs ); } -} +} \ No newline at end of file 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 0ae762f..c2e1909 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 @@ -21,4 +21,7 @@ public interface OrgService { // orgId 조직에서 memberId에 해당하는 맴버 제거 void removeMemberFromOrg(Long userId, Long orgId, Long memberId); + + // 조직 내 멤버 권한 변경 메서드 + 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 7033239..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 @@ -190,4 +190,34 @@ public void removeMemberFromOrg(Long userId, Long orgId, Long memberId) { // 5. 중간 테이블에서 해당 멤버 삭제 orgMemberRepository.delete(targetMember); } + + 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 (orgMember.getRole() == OrgRole.ADMIN) { + throw new OrgHandler(OrgErrorCode.ORG_CANNOT_ADMIN_TO_MEMBER); + } + + // 역할 변경 (더티체킹) + orgMember.updateRole(dto.orgRole()); + + // 변경된 멤버 정보를 DTO 로 반환 + return OrgConverter.toOrgMemberDTO(orgMember); + } } 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 fca280f..d2fa7ec 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,6 +12,7 @@ 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로 변경할 수 없습니다."), // 403 ORG_FORBIDDEN(HttpStatus.FORBIDDEN, "ORG_403_1", "해당 요청은 조직 생성자만 요청 가능합니다."), @@ -24,7 +25,6 @@ public enum OrgErrorCode implements BaseErrorCode { //409 ORG_ALREADY_ACTIVE(HttpStatus.CONFLICT, "ORG_409_1", "해당 조직은 이미 활성화 상태 입니다."), - //410 ORG_SOFT_DELETED(HttpStatus.GONE, "ORG_410_1", "해당 조직은 삭제된 조직입니다.(Soft Delete)"), ; 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 c3ac8f5..749d7f9 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 @@ -17,14 +17,7 @@ public interface OrgMemberRepository extends JpaRepository { //User 가 가진 OrgMember 모두 추출하는 메서드 List findOrgMemberByUser(User user); - - // 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); - + //userId 를 통해 OrgMember 추출 -> Organization 의 status 가 ACTIVE 인 경우에만 조회 @Query(value = "select om from OrgMember om join fetch om.organization o where om.user.id = :userId and o.status = 'ACTIVE'") List findOrgMemberByUserId(@Param("userId") Long userId); @@ -47,6 +40,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 77b8ded..f8474f0 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 @@ -125,4 +125,15 @@ public ResponseEntity> removeMember( orgService.removeMemberFromOrg(userId, orgId, memberId); return ResponseEntity.ok(DataResponse.from("해당 맴버가 조직에서 제외되었습니다.")); } + + @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 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 f6269a5..e73d578 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 @@ -134,4 +134,21 @@ public ResponseEntity> removeMember( @PathVariable Long orgId, @PathVariable Long memberId ); + + @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