Skip to content

Feat/#38 조직 맴버 관리 - 조직 맴버 권한 변경#43

Open
kingmingyu wants to merge 6 commits intodevelopfrom
feat/#38
Open

Feat/#38 조직 맴버 관리 - 조직 맴버 권한 변경#43
kingmingyu wants to merge 6 commits intodevelopfrom
feat/#38

Conversation

@kingmingyu
Copy link
Collaborator

@kingmingyu kingmingyu commented Feb 19, 2026

📌 관련 이슈

🚀 개요

이번 PR에서 변경된 핵심 내용을 요약해주세요.
API를 요청한 사용자의 권한을 확인하고(ADMIN인 경우만 실행 가능) 조직 id와 해당 맴버의 id, 바꾸려고 하는 역할을 입력받아 권한을 변경합니다.

📄 작업 내용

구체적인 작업 내용을 설명해주세요.

  • 요청한 사용자의 권한을 확인
  • 권한을 변경하려는 사용자의 역할을 확인 (ADMIN이라면 변경 불가)
  • 권한 변경

📸 스크린샷 / 테스트 결과 (선택)

결과물 확인을 위한 사진이나 테스트 로그를 첨부해주세요.

  • 변경 전
image
  • 실행
image image image
  • ADMIN 유저를 MEMBER로 변경 시도 (불가능)
image
  • ADMIN 권한이 없는 경우
image

✅ 체크리스트

  • 브랜치 전략(GitHub Flow)을 준수했나요?
  • 메서드 단위로 코드가 잘 쪼개져 있나요?
  • 테스트 통과 확인
  • 서버 실행 확인
  • API 동작 확인

🔍 리뷰 포인트 (Review Points)

리뷰어가 중점적으로 확인했으면 하는 부분을 적어주세요. (P1~P4 적용 가이드)

  • 여기도 마찬가지로 ADMIN을 MEMBER로 바꿀 수 없게 막아놨습니다. 근데 그렇게 될 경우에 한 번 ADMIN은 영원한 ADMIN이 되어 버리는데 괜찮을까요?

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

새로운 기능

  • 조직 내 구성원의 역할(ADMIN/MEMBER)을 업데이트할 수 있는 기능 추가
  • 관리자만 구성원의 역할 변경 가능
  • 관리자 역할에서 일반 구성원 역할로의 변경 시도 시 오류 메시지 표시

…ckend into feat/#38

# Conflicts:
#	src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java
#	src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java
#	src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java
#	src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java
#	src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java
@kingmingyu kingmingyu self-assigned this Feb 19, 2026
@kingmingyu kingmingyu added the ✨ Feature 새로운 기능 추가 label Feb 19, 2026
@kingmingyu kingmingyu linked an issue Feb 19, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Walkthrough

조직 멤버의 권한을 변경하는 기능을 추가합니다. PATCH 엔드포인트를 통해 요청 후 ADMIN 권한 검증, 권한 업데이트, 결과 반환의 흐름으로 구성됩니다.

Changes

Cohort / File(s) Summary
요청 및 변환 계층
OrgRequest.java, OrgConverter.java
UpdateRole 레코드 추가로 권한 변경 요청 페이로드 정의. OrgConverter에서 중복을 제거한 toOrgMemberDTO 헬퍼 메서드 추가.
서비스 계층
OrgService.java, OrgServiceImpl.java
updateOrgMembersRole 메서드 추가. 조직 존재 여부, ADMIN 권한, 대상 멤버 유효성 검증 후 더티 체킹으로 권한 업데이트.
엔티티 및 저장소
OrgMember.java, OrgMemberRepository.java
OrgMember에 updateRole 뮤테이터 메서드 추가. 저장소에서 메서드 위치 재정렬.
컨트롤러 및 문서
OrgController.java, OrgControllerDocs.java
PATCH /api/org/members/{orgId}/{memberId} 엔드포인트 추가. Swagger 문서 메서드 추가 (200, 401, 403, 404 응답).
예외 처리
OrgErrorCode.java
ORG_CANNOT_ADMIN_TO_MEMBER 에러 코드 추가 (ADMIN → MEMBER 변경 방지).

Sequence Diagram

sequenceDiagram
    actor Client
    participant Controller as OrgController
    participant Service as OrgServiceImpl
    participant Repo as OrgMemberRepository
    participant Entity as OrgMember
    
    Client->>Controller: PATCH /members/{orgId}/{memberId}<br/>UpdateRole{orgRole}
    
    Controller->>Service: updateOrgMembersRole(userId, orgId, memberId, dto)
    
    Service->>Repo: findByOrgIdAndId(orgId, ?)
    Repo-->>Service: Organization
    
    Service->>Repo: findByUserIdAndOrgId(userId, orgId)
    Repo-->>Service: OrgMember (requester)
    Note over Service: ✓ requester.role == ADMIN?
    
    Service->>Repo: findByOrgIdAndId(orgId, memberId)
    Repo-->>Service: OrgMember (target)
    Note over Service: ✓ target.role != ADMIN?
    
    Service->>Entity: updateRole(newOrgRole)
    Entity->>Entity: this.role = newOrgRole
    
    Service->>Repo: save(OrgMember)
    Repo-->>Service: OrgMember (updated)
    
    Service->>Service: toOrgMemberDTO(updated)
    Service-->>Controller: OrgMemberDTO
    
    Controller-->>Client: DataResponse{200, OrgMemberDTO}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

전체 9개 파일의 변경이 이루어졌으며, 권한 검증 로직(ADMIN 확인, ADMIN→MEMBER 변경 방지)이 포함되어 있습니다. 각 계층(Controller → Service → Entity)이 명확한 책임을 가지고 있어 흐름 파악은 쉽지만, 보안 관련 검증 로직을 꼼꼼히 확인해야 합니다. 특히 OrgServiceImpl.updateOrgMembersRole의 권한 체크와 ORG_CANNOT_ADMIN_TO_MEMBER 예외 처리를 검증하는 데 시간이 필요합니다.

Possibly related PRs

  • Feat/#19 #20: OrgRole 및 OrgMember 엔티티 기반 작업으로, 이 PR의 권한 변경 기능이 빌드되는 토대 제공
  • Feat/#23 #24: 조직 도메인 기본 구조(OrgService, OrgController 등) 도입으로, 이 PR에서 확장하는 동일 모듈 수정
  • Feat/#37 조직 맴버 관리 - 조직 맴버 삭제 #41: 동일한 조직 멤버 관리 모듈(서비스, 컨트롤러, 에러코드)을 수정하는 관련 멤버 관리 기능 (삭제 vs. 권한 변경)

Suggested reviewers

  • ojy0903
  • jinnieusLab

📋 리뷰 팁

잘 짠 부분들:

  • 계층 분리 명확함: DTO → Service → Entity 흐름이 깔끔하고, 각 계층의 책임이 잘 분리되었습니다.
  • 더티 체킹 활용: orgMember.updateRole() 후 저장소 호출로 JPA의 더티 체킹을 활용해 UPDATE 쿼리 1개만 발생합니다. 좋은 구현입니다!
  • 권한 검증 로직: ADMIN만 변경 가능하고, ADMIN을 MEMBER로 변경할 수 없도록 방어하는 로직이 있어 좋습니다.

꼼꼼히 확인할 부분들:

  • 🔍 OrgServiceImpl의 null 체크: findByOrgIdAndId(), findByUserIdAndOrgId() 쿼리 결과가 empty인 경우 어떻게 처리되는지 확인해주세요. Optional을 제대로 처리하지 않으면 NullPointerException이 발생할 수 있습니다.
  • 🔍 트랜잭션 관리: updateOrgMembersRole 메서드가 @Transactional로 선언되어 있는지 확인해주세요. 없으면 저장이 반영되지 않을 수 있습니다.
  • 🔍 권한 검증 순서: 현재 Organization 존재 확인 → requester 권한 확인 → target 유효성 확인 순서인데, 불필요한 쿼리를 줄이려면 순서 최적화를 고려해볼 수 있습니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 템플릿에 따라 잘 구성되어 있으며, 핵심 내용, 작업 내용, 테스트 결과, 체크리스트가 모두 완료되어 있습니다.
Linked Issues check ✅ Passed 코드 변경사항이 이슈 #38의 모든 요구사항을 충족합니다: 관리자 권한 확인, 멤버 권한 변경, 조직 확인 로직이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 조직 멤버 권한 변경 기능 구현과 직접 관련이 있으며, OrgMemberRepository 메서드 재정렬을 제외하고는 범위 내 변경입니다.
Title check ✅ Passed Pull request title clearly and specifically describes the main change: adding an API to change organization member roles, directly aligning with the changeset and PR objectives.

✏️ 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/#38

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

🧹 Nitpick comments (2)
src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java (1)

43-48: JPQL enum 비교에 문자열 리터럴 대신 파라미터 바인딩을 사용하는 것을 권장합니다.

현재 om.user.status = 'ACTIVE' 처럼 enum 값을 문자열 리터럴로 직접 비교하고 있습니다. UserStatus.ACTIVE enum의 이름이 변경되면 컴파일 에러 없이 런타임에서 쿼리가 잘못 동작할 수 있습니다.

파라미터 방식으로 변경하면 타입 안전성이 확보됩니다:

♻️ 파라미터 바인딩으로 변경 제안
-    `@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 " +
+            "WHERE om.user.id = :userId " +
+            "AND om.organization.id = :orgId " +
+            "AND om.user.status = :userStatus")
+    Optional<OrgMember> findByUserIdAndOrgId(`@Param`("userId") Long userId,
+                                              `@Param`("orgId") Long orgId,
+                                              `@Param`("userStatus") UserStatus userStatus);

호출부에서는 findByUserIdAndOrgId(userId, orgId, UserStatus.ACTIVE) 형태로 사용합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java`
around lines 43 - 48, Change the JPQL in OrgMemberRepository so it does not
compare the enum via the string literal 'ACTIVE'; update the query used by
findByUserIdAndOrgId (or add an overloaded method) to accept a UserStatus
parameter and bind it (e.g., "om.user.status = :status") and change the method
signature to include a UserStatus status parameter (call site will pass
UserStatus.ACTIVE). Ensure you reference the UserStatus enum and parameter name
(e.g., status) in the `@Param` annotation so om.user.status uses a typed parameter
instead of a hard-coded string.
src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java (1)

210-221: findByUserIdAndOrgId 결과에 JOIN FETCH 없이 toOrgMemberDTO 호출 시 추가 쿼리가 발생합니다.

findByUserIdAndOrgId JPQL은 om.user.status 조건으로 인해 user 테이블과 암묵적 JOIN을 하지만, JOIN FETCH가 없어서 user 엔티티를 실제로 로딩하지 않습니다.

이후 221번 라인에서 OrgConverter.toOrgMemberDTO(orgMember) 호출 시 orgMember.getUser().getName() 등으로 user에 접근하면 Hibernate가 별도 SELECT * FROM user WHERE id = ? 를 추가로 발생시킵니다.

이 메서드 전체로 보면 총 4번의 쿼리(조직 조회 1 + 요청자 조회 1 + 대상 멤버 조회 1 + user 지연 로딩 1)가 발생합니다.

대상 조회 쿼리에 JOIN FETCH를 추가하거나, 이 용도에 특화된 별도 쿼리 메서드를 추가하는 것을 권장합니다:

♻️ JOIN FETCH 추가 예시 (OrgMemberRepository)
// 권한 변경 응답용: user 정보도 함께 페치
`@Query`("SELECT om FROM OrgMember om " +
        "JOIN FETCH om.user u " +
        "WHERE om.user.id = :userId " +
        "AND om.organization.id = :orgId " +
        "AND u.status = :userStatus")
Optional<OrgMember> findByUserIdAndOrgIdWithUser(`@Param`("userId") Long userId,
                                                  `@Param`("orgId") Long orgId,
                                                  `@Param`("userStatus") UserStatus userStatus);

As per coding guidelines, "JPA 사용 시 N+1 문제나 불필요한 쿼리가 발생하지 않는지" 확인이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`
around lines 210 - 221, The current retrieval uses
orgMemberRepository.findByUserIdAndOrgId which does not fetch the associated
User, causing a lazy-load when OrgConverter.toOrgMemberDTO(orgMember) accesses
orgMember.getUser(); change the service to use a repository method that JOIN
FETCHes the user (e.g., add and call findByUserIdAndOrgIdWithUser on
OrgMemberRepository that joins FETCH om.user and filters by user status) or
create a dedicated query returning the OrgMember with user eagerly loaded, then
use that result for updateRole and OrgConverter.toOrgMemberDTO to avoid the
extra select.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`:
- Around line 197-199: The method updateOrgMembersRole in OrgServiceImpl fetches
Organization organization but never checks its status, allowing role changes on
soft-deleted orgs; add a check after retrieving organization: if
organization.getStatus() == OrgStatus.DELETED then throw new
OrgHandler(OrgErrorCode.ORG_SOFT_DELETED) (same pattern as
getOrganizationDetail) so updates are blocked for soft-deleted organizations.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java`:
- Line 144: Update the ApiResponse for the ADMIN→MEMBER change in
OrgControllerDocs.java: replace the incorrect responseCode "401" with "400" and
adjust the description accordingly (the annotation on the ApiResponse for the
business-rule rejection in OrgControllerDocs should reflect Bad Request); locate
the ApiResponse entry in the OrgControllerDocs class that currently reads
responseCode = "401" and change it to responseCode = "400" so it matches the
ORG_400_4 error and actual response payload.
- Line 143: The `@ApiResponse` description for responseCode "200" in
OrgControllerDocs is incorrectly copied from getOrgMembersCount; update the
description for the 권한 변경 API to reflect that it returns the changed member info
(OrgMemberDTO). Locate the `@ApiResponse`(...) annotation in OrgControllerDocs for
the permission-change endpoint and replace the text "성공 (totalCount: 전체 멤버 수)"
with a concise message such as "성공 (변경된 멤버 정보 반환: OrgMemberDTO)" or equivalent
that mentions OrgMemberDTO.
- Line 152: The UpdateRole request parameter is missing `@Valid` so Bean
Validation on OrgRequest.UpdateRole.orgRole (annotated `@NotNull`) won't run;
update the controller method signature that accepts OrgRequest.UpdateRole (the
parameter currently declared as "@RequestBody OrgRequest.UpdateRole dto") to use
"@RequestBody `@Valid` OrgRequest.UpdateRole dto" so Spring triggers validation
(mirror the existing approach used in createOrganization/modifyOrganization).

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java`:
- Around line 129-138: The updateOrgMembersRole controller is missing request
validation so OrgRequest.UpdateRole's `@NotNull` on orgRole isn't enforced; add
`@Valid` to the controller parameter (change the signature of updateOrgMembersRole
to accept `@RequestBody` `@Valid` OrgRequest.UpdateRole dto) so Spring performs
validation before calling orgService.updateOrgMembersRole and avoids null being
passed into orgMember.updateRole/OrgConverter.toOrgMemberDTO causing NPEs;
ensure any BindingResult or global exception handler already handles
MethodArgumentNotValidException appropriately.

---

Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`:
- Around line 210-221: The current retrieval uses
orgMemberRepository.findByUserIdAndOrgId which does not fetch the associated
User, causing a lazy-load when OrgConverter.toOrgMemberDTO(orgMember) accesses
orgMember.getUser(); change the service to use a repository method that JOIN
FETCHes the user (e.g., add and call findByUserIdAndOrgIdWithUser on
OrgMemberRepository that joins FETCH om.user and filters by user status) or
create a dedicated query returning the OrgMember with user eagerly loaded, then
use that result for updateRole and OrgConverter.toOrgMemberDTO to avoid the
extra select.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java`:
- Around line 43-48: Change the JPQL in OrgMemberRepository so it does not
compare the enum via the string literal 'ACTIVE'; update the query used by
findByUserIdAndOrgId (or add an overloaded method) to accept a UserStatus
parameter and bind it (e.g., "om.user.status = :status") and change the method
signature to include a UserStatus status parameter (call site will pass
UserStatus.ACTIVE). Ensure you reference the UserStatus enum and parameter name
(e.g., status) in the `@Param` annotation so om.user.status uses a typed parameter
instead of a hard-coded string.

Comment on lines +197 to +199
// 1. 조직 존재 여부 확인
Organization organization = orgRepository.findById(orgId)
.orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_NOT_FOUND));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

소프트 삭제된 조직에 대한 권한 변경이 가능합니다 — OrgStatus.DELETED 체크가 누락되었습니다.

Organization organization 변수를 할당했지만 이후 한 번도 사용하지 않습니다. 기존 getOrganizationDetail 메서드를 보면:

if (organization.getStatus() == OrgStatus.DELETED) {
    throw new OrgHandler(OrgErrorCode.ORG_SOFT_DELETED);
}

이 체크가 존재합니다. 현재 updateOrgMembersRole에서는 이 체크가 없어서 소프트 삭제된 조직(status = 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
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`
around lines 197 - 199, The method updateOrgMembersRole in OrgServiceImpl
fetches Organization organization but never checks its status, allowing role
changes on soft-deleted orgs; add a check after retrieving organization: if
organization.getStatus() == OrgStatus.DELETED then throw new
OrgHandler(OrgErrorCode.ORG_SOFT_DELETED) (same pattern as
getOrganizationDetail) so updates are blocked for soft-deleted organizations.

description = "맴버 권한 변경을 요청한 유저의 권한이 ADMIN인 경우 실행이 가능합니다. memberId에 해당하는 맴버의 권한을 변경시킵니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 수)"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

200 응답 설명이 잘못된 값으로 복붙되었습니다.

"성공 (totalCount: 전체 멤버 수)"getOrgMembersCount API의 설명을 그대로 가져온 것입니다. 권한 변경 API의 성공 응답에는 변경된 멤버 정보(OrgMemberDTO)가 반환되므로 설명을 수정해야 합니다.

📝 수정 제안
-@ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 수)"),
+@ApiResponse(responseCode = "200", description = "성공 (변경된 멤버 정보 반환)"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 )"),
`@ApiResponse`(responseCode = "200", description = "성공 (변경된 멤버 정보 반환)"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java`
at line 143, The `@ApiResponse` description for responseCode "200" in
OrgControllerDocs is incorrectly copied from getOrgMembersCount; update the
description for the 권한 변경 API to reflect that it returns the changed member info
(OrgMemberDTO). Locate the `@ApiResponse`(...) annotation in OrgControllerDocs for
the permission-change endpoint and replace the text "성공 (totalCount: 전체 멤버 수)"
with a concise message such as "성공 (변경된 멤버 정보 반환: OrgMemberDTO)" or equivalent
that mentions OrgMemberDTO.

)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공 (totalCount: 전체 멤버 수)"),
@ApiResponse(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

잘못된 HTTP 상태 코드: 401이 아니라 400이 맞습니다.

HTTP 401 Unauthorized는 인증(Authentication) 실패를 의미합니다. ADMIN→MEMBER 변경을 거부하는 것은 비즈니스 로직 규칙 위반이므로 400 Bad Request가 올바릅니다. PR 스크린샷에서도 실제 에러 응답이 "status": "Bad Request" (ORG_400_4)임을 확인할 수 있습니다.

예시:

  • 401: 토큰이 없거나 만료된 경우 (인증 실패)
  • 400: ADMIN을 MEMBER로 변경하려는 잘못된 요청 (비즈니스 규칙 위반)
📝 수정 제안
-@ApiResponse(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),
+@ApiResponse(responseCode = "400", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ApiResponse(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),
`@ApiResponse`(responseCode = "400", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java`
at line 144, Update the ApiResponse for the ADMIN→MEMBER change in
OrgControllerDocs.java: replace the incorrect responseCode "401" with "400" and
adjust the description accordingly (the annotation on the ApiResponse for the
business-rule rejection in OrgControllerDocs should reflect Bad Request); locate
the ApiResponse entry in the OrgControllerDocs class that currently reads
responseCode = "401" and change it to responseCode = "400" so it matches the
ORG_400_4 error and actual response payload.

@AuthenticationPrincipal(expression = "userId") Long userId,
@PathVariable Long orgId,
@PathVariable Long memberId,
@RequestBody OrgRequest.UpdateRole dto
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

@RequestBody@Valid가 누락되었습니다 — @NotNull 검증이 동작하지 않습니다.

AI 요약에 따르면 OrgRequest.UpdateRoleorgRole 필드에 @NotNull이 선언되어 있습니다. 그런데 @Valid가 없으면 Spring은 Bean Validation을 실행하지 않아서, orgRolenull인 요청이 그대로 서비스 레이어까지 내려갑니다. 이는 NPE나 예상치 못한 DB 오류로 이어질 수 있습니다.

다른 메서드들(예: Line 24 createOrganization, Line 62 modifyOrganization)은 모두 @RequestBody @Valid``를 사용하고 있습니다.

🛡️ 수정 제안
-@RequestBody OrgRequest.UpdateRole dto
+@RequestBody `@Valid` OrgRequest.UpdateRole dto
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java`
at line 152, The UpdateRole request parameter is missing `@Valid` so Bean
Validation on OrgRequest.UpdateRole.orgRole (annotated `@NotNull`) won't run;
update the controller method signature that accepts OrgRequest.UpdateRole (the
parameter currently declared as "@RequestBody OrgRequest.UpdateRole dto") to use
"@RequestBody `@Valid` OrgRequest.UpdateRole dto" so Spring triggers validation
(mirror the existing approach used in createOrganization/modifyOrganization).

Comment on lines +129 to +138
@PatchMapping("/members/{orgId}/{memberId}")
public ResponseEntity<DataResponse<OrgResponse.OrgMemberDTO>> 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));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🚨 @Valid 누락 — null orgRole이 입력되면 런타임 NPE가 발생합니다!

@RequestBody OrgRequest.UpdateRole dto@Valid가 없습니다. 다른 엔드포인트들을 보면:

  • @PostMapping("/create")@RequestBody @Valid OrgRequest.Create request
  • @PatchMapping("/{orgId}")@RequestBody @Valid OrgRequest.Update request

현재 엔드포인트는 @Valid가 없어서 OrgRequest.UpdateRole@NotNull(message = "역할은 필수입니다.") 검증이 전혀 실행되지 않습니다.

클라이언트가 {"orgRole": null} 또는 {} 를 전송하면 다음 흐름이 발생합니다:

  1. dto.orgRole()null
  2. orgMember.updateRole(null)this.role = null (메모리)
  3. OrgConverter.toOrgMemberDTO(orgMember)orgMember.getRole().name() 💥 NullPointerException
🐛 수정 방법
-    `@RequestBody` OrgRequest.UpdateRole dto
+    `@RequestBody` `@Valid` OrgRequest.UpdateRole dto
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java`
around lines 129 - 138, The updateOrgMembersRole controller is missing request
validation so OrgRequest.UpdateRole's `@NotNull` on orgRole isn't enforced; add
`@Valid` to the controller parameter (change the signature of updateOrgMembersRole
to accept `@RequestBody` `@Valid` OrgRequest.UpdateRole dto) so Spring performs
validation before calling orgService.updateOrgMembersRole and avoids null being
passed into orgMember.updateRole/OrgConverter.toOrgMemberDTO causing NPEs;
ensure any BindingResult or global exception handler already handles
MethodArgumentNotValidException appropriately.

@AuthenticationPrincipal(expression = "userId") Long userId,
@PathVariable Long orgId,
@PathVariable Long memberId,
@RequestBody OrgRequest.UpdateRole dto
Copy link
Collaborator

@ojy0903 ojy0903 Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Controller 메서드에서 OrgRequest.UpdateRole 필드에 Valid 어노테이션 달아서 NotNull 검사 적용되게 해주면 좋을 것 같아요!

Copy link
Collaborator

@ojy0903 ojy0903 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P4: 고생하셨어요! 제가 보기엔 ADMIN 인 사용자를 MEMBER 로 바꾸지 못하면 문제가 생길 수도 있을 것 같은데, 아예 OrgRole 에 CREATOR 같이 최고 권한 역할을 만드는게 좋을까요? 아니면 지금처럼 ADMIN 은 MEMBER 로 바꾸지 못하도록 하되 화면 UI 에서 사용자에게 경고를 주는 방식으로 하는 게 맞을까요? 지민님 의견도 들어봐야 될 듯 합니다...

@kingmingyu kingmingyu changed the title Feat/#38 Feat/#38 조직 맴버 관리 - 조직 맴버 권한 변경 Feb 19, 2026
@kingmingyu
Copy link
Collaborator Author

P4: 고생하셨어요! 제가 보기엔 ADMIN 인 사용자를 MEMBER 로 바꾸지 못하면 문제가 생길 수도 있을 것 같은데, 아예 OrgRole 에 CREATOR 같이 최고 권한 역할을 만드는게 좋을까요? 아니면 지금처럼 ADMIN 은 MEMBER 로 바꾸지 못하도록 하되 화면 UI 에서 사용자에게 경고를 주는 방식으로 하는 게 맞을까요? 지민님 의견도 들어봐야 될 듯 합니다...

저도 뭔가 계속 새로운 권한을 만들면 끝이 없을 것 같아서 고민 중이었습니다..! 최고 권한 역할도 좋습니다 뭔가 ADMIN도 강등할 수 있는 방법이 필요할 것 같긴 합니다!

Copy link
Collaborator

@jinnieusLab jinnieusLab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: 고생하셨습니다! 제 생각에도 ADMIN이 MEMBER 권한으로 변경될 수 없다면 문제가 있을 것 같습니다.. 해당 조직 내 ADMIN이 두 명 이상 존재한다면 ADMIN -> MEMBER를 가능하게 하는 건 어떨까요?

만약 추가로 더 구현한다면, 조직 내 ADMIN이 오직 한 명이면 관리자를 다른 이로 임명하게끔 구현해도 될 듯 합니다! (+ ADMIN 권한인 유저 삭제(추방)하는 것도 비슷하게 고민해볼 수 있을 것 같습니다..)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 조직 맴버 관리 - 조직 맴버 권한 변경

3 participants

Comments