-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#38 조직 맴버 관리 - 조직 맴버 권한 변경 #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1333989
4cb4ebc
c1c35e0
5233625
b1e026c
9fffcdc
c11a33b
d8425d0
39b1f09
64d91d2
86c8bb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -197,6 +197,47 @@ public void removeMemberFromOrg(Long userId, Long orgId, Long memberId) { | |
| orgMemberRepository.delete(targetMember); | ||
| } | ||
|
|
||
| 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)); | ||
|
Comment on lines
+208
to
+210
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 소프트 삭제된 조직에 대한 권한 변경이 가능합니다 —
if (organization.getStatus() == OrgStatus.DELETED) {
throw new OrgHandler(OrgErrorCode.ORG_SOFT_DELETED);
}이 체크가 존재합니다. 현재 🐛 소프트 삭제 체크 추가 제안 Organization organization = orgRepository.findById(orgId)
.orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_NOT_FOUND));
+
+// Soft Delete 된 조직이면 예외처리
+if (organization.getStatus() == OrgStatus.DELETED) {
+ throw new OrgHandler(OrgErrorCode.ORG_SOFT_DELETED);
+}🤖 Prompt for AI Agents |
||
|
|
||
| // 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. 권한 변경 대상 멤버 조회 | ||
| OrgMember orgMember = orgMemberRepository.findByUserIdAndOrgId(memberId, orgId) | ||
| .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND)); | ||
|
|
||
| // 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); | ||
| } | ||
| } | ||
|
|
||
| // 역할 변경 (더티체킹) | ||
| orgMember.updateRole(dto.orgRole()); | ||
|
|
||
| // 변경된 멤버 정보를 DTO 로 반환 | ||
| return OrgConverter.toOrgMemberDTO(orgMember); | ||
| } | ||
|
|
||
| @Override | ||
| // 조직 초대 이메일 보내기 | ||
| public OrgResponse.OrgInvitationResponse sendOrgInvitation(Long userId, Long orgId, String email) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
@@ -17,14 +18,7 @@ public interface OrgMemberRepository extends JpaRepository<OrgMember, Long> { | |
|
|
||
| //User 가 가진 OrgMember 모두 추출하는 메서드 | ||
| List<OrgMember> 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<OrgMember> 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<OrgMember> findOrgMemberByUserId(@Param("userId") Long userId); | ||
|
|
@@ -47,6 +41,13 @@ Slice<OrgMember> 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<OrgMember> findByUserIdAndOrgId(@Param("userId") Long userId, @Param("orgId") Long orgId); | ||
|
Comment on lines
+44
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 1. OrgMemberRepository 위치 및 내용 확인
echo "=== Finding OrgMemberRepository ==="
find . -name "OrgMemberRepository.java" -type f 2>/dev/null
echo ""
echo "=== OrgMemberRepository 전체 내용 ==="
find . -name "OrgMemberRepository.java" -type f -exec cat {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 2907 🏁 Script executed: #!/bin/bash
# 2. OrgServiceImpl 확인 - updateOrgMembersRole 메서드
echo "=== Finding OrgServiceImpl ==="
find . -name "OrgServiceImpl.java" -type f 2>/dev/null
echo ""
echo "=== OrgServiceImpl 내용 ==="
find . -name "OrgServiceImpl.java" -type f -exec cat {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 12628 🏁 Script executed: #!/bin/bash
# 3. OrgConverter 확인 - toOrgMemberDTO 메서드
echo "=== Finding OrgConverter ==="
find . -name "OrgConverter.java" -type f 2>/dev/null
echo ""
echo "=== OrgConverter 내용 ==="
find . -name "OrgConverter.java" -type f -exec cat {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 3746 🏁 Script executed: #!/bin/bash
# 4. User entity 확인 - OrgMember와의 관계 설정 확인
echo "=== Finding User entity ==="
find . -name "User.java" -path "*/user/persistence/entity/*" -type f 2>/dev/null
echo ""
echo "=== User entity 내용 ==="
find . -name "User.java" -path "*/user/persistence/entity/*" -type f -exec head -100 {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 1739 🏁 Script executed: #!/bin/bash
# 5. OrgMember entity 확인 - User와의 FetchType 설정 확인
echo "=== Finding OrgMember entity ==="
find . -name "OrgMember.java" -type f 2>/dev/null
echo ""
echo "=== OrgMember entity 내용 ==="
find . -name "OrgMember.java" -type f -exec cat {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 1318 🏁 Script executed: #!/bin/bash
# 6. removeMemberFromOrg와 다른 메서드에서 findByUserIdAndOrgId 사용 패턴 확인
echo "=== Checking removeMemberFromOrg method usage ==="
rg -n -B 2 -A 8 "findByUserIdAndOrgId" src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java
echo ""
echo "=== UserStatus enum definition ==="
find . -name "UserStatus.java" -type f -exec cat {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 1988 🏁 Script executed: #!/bin/bash
# 7. updateOrgMembersRole 메서드 호출 이후 user 필드 접근 확인
echo "=== Detailed updateOrgMembersRole flow ==="
sed -n '203,226p' src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java
echo ""
echo "=== Check if OrgMemberDTO uses user fields ==="
find . -name "OrgResponse.java" -type f -exec grep -A 5 "class OrgMemberDTO" {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 1152 🏁 Script executed: #!/bin/bash
# 8. Find OrgResponse class with OrgMemberDTO record definition
echo "=== Finding OrgResponse ==="
find . -name "OrgResponse.java" -type f 2>/dev/null
echo ""
echo "=== OrgResponse class full content ==="
find . -name "OrgResponse.java" -type f -exec cat {} \;Repository: WhereYouAd/WhereYouAd-Backend Length of output: 2051 🏁 Script executed: #!/bin/bash
# 9. Check all usages of findByUserIdAndOrgId to see if user fields are accessed
echo "=== All usages of findByUserIdAndOrgId ==="
rg -n "findByUserIdAndOrgId" src/main/java --type java -B 2 -A 10 | head -100
echo ""
echo "=== Check if user field is accessed in removeMemberFromOrg ==="
sed -n '170,197p' src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.javaRepository: WhereYouAd/WhereYouAd-Backend Length of output: 9119 🏁 Script executed: #!/bin/bash
# 10. Final check - see the complete return statement for updateOrgMembersRole
echo "=== Complete updateOrgMembersRole method (end) ==="
sed -n '226,230p' src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.javaRepository: WhereYouAd/WhereYouAd-Backend Length of output: 230 🏁 Script executed: #!/bin/bash
# Final verification - check how many times findByUserIdAndOrgId is used and in which contexts
echo "=== Summary of findByUserIdAndOrgId usage patterns ==="
rg -n "findByUserIdAndOrgId" src/main/java --type java | grep -v "Query\|Parameter\|Optional" | head -20Repository: WhereYouAd/WhereYouAd-Backend Length of output: 878
검증 결과, 문제 시나리오:
클래스 레벨의 같은 레포지토리의 추가로 ♻️ 수정 제안-@Query("SELECT om FROM OrgMember om " +
- "WHERE om.user.id = :userId " +
- "AND om.organization.id = :orgId " +
- "AND om.user.status = 'ACTIVE'")
-Optional<OrgMember> findByUserIdAndOrgId(`@Param`("userId") Long userId, `@Param`("orgId") Long orgId);
+@Query("SELECT om FROM OrgMember om " +
+ "JOIN FETCH om.user u " +
+ "WHERE u.id = :userId " +
+ "AND om.organization.id = :orgId " +
+ "AND u.status = :status")
+Optional<OrgMember> findByUserIdAndOrgId(
+ `@Param`("userId") Long userId,
+ `@Param`("orgId") Long orgId,
+ `@Param`("status") UserStatus status
+);호출부( 🤖 Prompt for AI Agents |
||
|
|
||
| // 조직의 전체 멤버 수 조회 | ||
| @Query("SELECT COUNT(m) FROM OrgMember m " + | ||
| "JOIN m.user u " + | ||
|
|
@@ -58,4 +59,7 @@ int countByOrganizationIdAndUserStatus( | |
| ); | ||
|
|
||
| Boolean existsByUserAndOrganization(User user, Organization organization); | ||
|
|
||
| // 조직 id에 해당하는 역할 인원 수 조회 | ||
| long countByOrganizationIdAndRole(Long orgId, OrgRole orgRole); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -135,6 +135,23 @@ public ResponseEntity<DataResponse<String>> removeMember( | |||||
| @PathVariable Long memberId | ||||||
| ); | ||||||
|
|
||||||
| @Operation( | ||||||
| summary = "조직 맴버 권한 변경 API", | ||||||
| description = "맴버 권한 변경을 요청한 유저의 권한이 ADMIN인 경우 실행이 가능합니다. memberId에 해당하는 맴버의 권한을 변경시킵니다." | ||||||
| ) | ||||||
| @ApiResponses({ | ||||||
| @ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 수)"), | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 200 응답 설명이 잘못된 값으로 복붙되었습니다.
📝 수정 제안-@ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 수)"),
+@ApiResponse(responseCode = "200", description = "성공 (변경된 멤버 정보 반환)"),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| @ApiResponse(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"), | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 잘못된 HTTP 상태 코드: 401이 아니라 400이 맞습니다. HTTP 예시:
📝 수정 제안-@ApiResponse(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),
+@ApiResponse(responseCode = "400", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| @ApiResponse(responseCode = "403", description = "권한이 부족한 경우(요청을 보낸 유저의 권한이 ADMIN이 아닌 경우)"), | ||||||
| @ApiResponse(responseCode = "404", description = "해당 id의 데이터 존재 X") | ||||||
| }) | ||||||
| ResponseEntity<DataResponse<OrgResponse.OrgMemberDTO>> updateOrgMembersRole( | ||||||
| @AuthenticationPrincipal(expression = "userId") Long userId, | ||||||
| @PathVariable Long orgId, | ||||||
| @PathVariable Long memberId, | ||||||
| @RequestBody OrgRequest.UpdateRole dto | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
AI 요약에 따르면 다른 메서드들(예: Line 24 🛡️ 수정 제안-@RequestBody OrgRequest.UpdateRole dto
+@RequestBody `@Valid` OrgRequest.UpdateRole dto🤖 Prompt for AI Agents |
||||||
| ); | ||||||
|
|
||||||
| @Operation(summary = "조직 초대 이메일 발송 API", description = "조직 관리자가 이메일을 입력하여 새로운 멤버를 초대합니다.") | ||||||
| @ApiResponses({ | ||||||
| @ApiResponse(responseCode = "200", description = "성공"), | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: WhereYouAd/WhereYouAd-Backend
Length of output: 18113
memberId파라미터명이 API 경로와 실제 동작이 일치하지 않습니다. 명확히 수정이 필요합니다.API 경로
/members/{orgId}/{memberId}에서memberId는OrgMember의 PK를 의미하는 것처럼 보이지만, 실제로는 User의 ID입니다.증거:
findByUserIdAndOrgId(memberId, orgId)에서memberId를userId파라미터 위치에 전달findByUserIdAndOrgId(@param("userId") Long userId,@param("orgId") Long orgId)문제점:
OrgMemberPK를 보낼 가능성 높음 → "member not found" 오류 발생removeMemberFromOrg()도 동일한 패턴을 가지고 있음해결책:
memberId→targetUserId로 변경하거나/members/{orgId}/{userId}로 변경memberId가 User ID임을 기록removeMemberFromOrg()메서드도 동일하게 수정하세요.🤖 Prompt for AI Agents