Skip to content

Feat/#37 조직 맴버 관리 - 조직 맴버 삭제#41

Merged
kingmingyu merged 5 commits intodevelopfrom
feat/#37
Feb 19, 2026
Merged

Feat/#37 조직 맴버 관리 - 조직 맴버 삭제#41
kingmingyu merged 5 commits intodevelopfrom
feat/#37

Conversation

@kingmingyu
Copy link
Collaborator

@kingmingyu kingmingyu commented Feb 18, 2026

📌 관련 이슈

🚀 개요

이번 PR에서 변경된 핵심 내용을 요약해주세요.
조직 id와 삭제하려는 맴버의 id를 받아서 ADMIN 권한이 있다면 해당 맴버를 중간 테이블에서 삭제합니다.

📄 작업 내용

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

  • 요청을 보낸 유저의 권한 확인
  • 삭제하려는 맴버의 존재 유무와 권한 확인(ADMIN인 경우 추방 불가)
  • 해당 맴버 중간 테이블에서 제거

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

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

  1. 현재 테이블 상태 (10번 유저는 DELETED 상태)
image
  1. 9번 유저 삭제하기
image image
  1. 본인 삭제(불가)
image
  1. ADMIN 삭제(불가)
image image

✅ 체크리스트

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

🔍 리뷰 포인트 (Review Points)

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

  1. 본인 삭제는 불가하도록 막았습니다.
  2. 같은 ADMIN끼리는 추방 불가능 하도록 막았는데 괜찮을까요??

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

Summary by CodeRabbit

  • 새로운 기능
    • 조직에서 멤버를 제거하는 기능이 추가되었습니다. 관리자 권한을 가진 사용자만 사용 가능하며, 자신의 계정이나 다른 관리자 계정은 제거할 수 없습니다.
    • 멤버 제거를 위한 REST API(DELETE /api/org/{orgId}/members/{memberId})와 관련 문서가 추가되었고, 실패 상황에 대한 명확한 오류 메시지가 제공됩니다.

@kingmingyu kingmingyu self-assigned this Feb 18, 2026
@kingmingyu kingmingyu added the ✨ Feature 새로운 기능 추가 label Feb 18, 2026
@kingmingyu kingmingyu linked an issue Feb 18, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Walkthrough

조직 관리자 권한으로 조직에 속한 특정 멤버를 삭제하는 기능이 추가되었습니다. DELETE 엔드포인트를 통해 요청을 받고, 자기 추방 금지·조직 존재 확인·요청자 ADMIN 권한 검증·대상 멤버 존재 및 ADMIN 추방 금지 검사를 수행한 뒤 멤버 레코드를 삭제합니다.

Changes

Cohort / File(s) Summary
서비스 계층
src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java, src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java
removeMemberFromOrg(Long userId, Long orgId, Long memberId) 메서드 추가. 자기 추방 방지, 조직 존재 확인, 요청자 ADMIN 권한 검증, 대상 멤버 존재 확인, ADMIN 추방 방지 및 삭제 로직 구현.
리포지토리
src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java
Optional<OrgMember> findByUserIdAndOrgId(Long userId, Long orgId) 조회 메서드 추가(활성 상태 필터 포함).
에러 처리
src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java
새 에러 코드 4개 추가: ORG_CANNOT_KICK_SELF, ORG_CANNOT_KICK_ADMIN, ORG_MEMBER_FORBIDDEN, ORG_MEMBER_NOT_FOUND. 일부 enum 포맷 조정.
프레젠테이션 계층
src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java, src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java
DELETE /api/org/{orgId}/members/{memberId} 엔드포인트 및 Swagger 문서용 메서드 추가. 인증된 userId를 받아 서비스 호출 후 성공 메시지 반환.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Ctrl as OrgController
    participant Svc as OrgService
    participant OrgRepo as OrgRepository
    participant MemRepo as OrgMemberRepository
    Client->>Ctrl: DELETE /api/org/{orgId}/members/{memberId} (auth userId)
    Ctrl->>Svc: removeMemberFromOrg(userId, orgId, memberId)
    Svc->>Svc: validate not self (userId != memberId)
    Svc->>OrgRepo: findById(orgId)
    OrgRepo-->>Svc: Organization
    Svc->>MemRepo: findByUserIdAndOrgId(userId, orgId)  -- 확인 요청자 권한(ADMIN)
    MemRepo-->>Svc: OrgMember(requester)
    Svc->>MemRepo: findByUserIdAndOrgId(memberId, orgId) -- 대상 멤버 조회
    MemRepo-->>Svc: OrgMember(target)
    Svc->>Svc: validate target not ADMIN
    Svc->>MemRepo: delete(target)
    MemRepo-->>Svc: deleted
    Svc-->>Ctrl: success
    Ctrl-->>Client: 200 DataResponse<String>("멤버를 추방했습니다.")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Feat/#19 #20: OrgRole과 OrgMember 엔티티/리포지토리 기초 작업 — 삭제 로직이 해당 타입/리포지토리를 직접 사용함으로써 강한 연관성이 있음

Suggested reviewers

  • jinnieusLab

코드 리뷰 포인트

  • 잘 된 점

    • 다단계 검증(자기 추방 금지, 조직 존재 확인, 요청자 ADMIN 검증, 대상 존재 및 ADMIN 방지)이 명확히 구현되어 비즈니스 규칙이 잘 반영되어 있습니다.
    • OrgMemberRepository의 활성 상태 필터를 명시적으로 쿼리에 포함한 점이 안전합니다.
  • 주의할 점

    • 검증 순서: 가장 비용이 적은 검증(예: 자기 추방)부터 검사해 불필요한 DB 조회를 줄이는 것이 좋습니다. 현재 구현에서 조회 순서가 뒤바뀌어 있다면 간단히 앞쪽으로 옮기세요.
    • DB 쿼리 수: 요청자 권한 확인과 대상 멤버 조회 등으로 쿼리가 여러 번 발생합니다. 대량 작업이 아닌 일반 사용에선 괜찮지만, 향후 배치/대량 삭제 시 페치 전략이나 트랜잭션 범위를 재검토하세요.
    • 예외 메시지·상태코드: 새로 추가된 OrgErrorCode들이 프론트엔드에서 의도한 처리 흐름(400 vs 403 vs 404)에 맞는지 확인하세요.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% 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
Title check ✅ Passed PR 제목은 이슈 #37 작업(조직 맴버 삭제)을 명확히 반영하며, 핵심 변경사항을 간결하게 요약합니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(관련 이슈, 개요, 작업 내용, 테스트 결과, 체크리스트)을 포함하고 구체적인 구현 내용과 테스트 증거를 제시합니다.
Linked Issues check ✅ Passed PR의 구현이 이슈 #37의 모든 요구사항을 충족합니다: ADMIN 권한 검증, 맴버 존재 여부 확인, 중간 테이블에서 제거 기능이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 맴버 삭제 기능 구현에 직접 관련되어 있으며, 범위를 벗어난 변경사항이 없습니다.

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

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 (1)
src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java (1)

109-109: @DeleteMapping에서 앞 슬래시(/) 누락 — 다른 엔드포인트와 일관성이 없습니다.

기존 모든 메서드(@PatchMapping("/{orgId}"), @PatchMapping("/{orgId}/restore"), 등)는 경로를 /로 시작합니다. @DeleteMapping("{orgId}/members/{memberId}")는 앞 슬래시가 없어 일관성이 깨집니다.

Spring Boot 3.x의 PathPatternParser가 경로를 자동으로 정규화하므로 런타임에서 동작하더라도, 코드 가독성과 팀 컨벤션 측면에서 통일해 주세요.

✏️ 수정 제안
-    `@DeleteMapping`("{orgId}/members/{memberId}")
+    `@DeleteMapping`("/{orgId}/members/{memberId}")
🤖 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`
at line 109, In OrgController, the `@DeleteMapping` on the delete member endpoint
is missing the leading slash; update the annotation on the method that handles
deleting members (the `@DeleteMapping`("{orgId}/members/{memberId}") declaration)
to include a leading "/" (i.e., `@DeleteMapping`("/{orgId}/members/{memberId}"))
so it matches the style of other mappings like `@PatchMapping`("/{orgId}") and
preserves path consistency and readability.
🤖 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 150-155: Change the behavior in OrgServiceImpl where you fetch the
requester via orgMemberRepository.findByUserIdAndOrgId(...) so that both "not a
member" and "not ADMIN" cases throw OrgHandler with
OrgErrorCode.ORG_MEMBER_FORBIDDEN instead of returning ORG_MEMBER_NOT_FOUND;
specifically replace the orElseThrow(...) that currently uses
ORG_MEMBER_NOT_FOUND to use ORG_MEMBER_FORBIDDEN, leaving the subsequent role
check throwing ORG_MEMBER_FORBIDDEN intact so both branches consistently return
403.
- Around line 44-49: The createOrganization method in OrgServiceImpl triggers an
N+1 query because orgMemberRepository.findOrgMemberByUser(user) returns
OrgMember entities whose organization is LAZY and you call
orgMember.getOrganization().getName() in a loop; fix this by updating the
repository: either change findOrgMemberByUser to fetch organizations with a JOIN
FETCH so Organization is loaded with OrgMember, or (preferred for simple
duplicate checks) add a repository method existsByUserAndOrganizationName(user,
name) that runs a single query to test name existence and call that from
createOrganization instead of iterating orgMemberRepository.findOrgMemberByUser;
update OrgServiceImpl.createOrganization to use the new repository method (or
the JOIN FETCH variant) and remove the loop-based name checks to eliminate the
N+1 queries.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java`:
- Around line 22-26: The findByUserIdAndOrgId method in OrgMemberRepository
currently hardcodes 'ACTIVE' in the JPQL; change the query to use a status
parameter (e.g. "AND om.user.status = :status"), update the method signature to
accept `@Param`("status") UserStatus status, and ensure callers (like
OrgServiceImpl) pass UserStatus.ACTIVE; this makes the method type-safe and
consistent with findByOrganizationIdWithCursor and
countByOrganizationIdAndUserStatus.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java`:
- Around line 104-108: The Swagger docs for OrgController are missing 400
responses for cases thrown by OrgServiceImpl.removeMemberFromOrg; update the
`@ApiResponses` array in OrgControllerDocs (where `@ApiResponses/`@ApiResponse are
declared) to include two `@ApiResponse` entries with responseCode = "400" and
descriptions for ORG_400_2 ("cannot remove yourself") and ORG_400_3 ("cannot
remove ADMIN"), so clients see the BAD_REQUEST cases that removeMemberFromOrg
can return.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java`:
- Line 116: In OrgController (method returning
ResponseEntity.ok(DataResponse.from(...))) fix the Korean typo in the response
string by changing "해당 맴버가 조직에서 제외되었습니다." to "해당 멤버가 조직에서 제외되었습니다." so it
matches other docs (e.g., OrgControllerDocs) and maintains consistent messaging;
locate the DataResponse.from(...) call in OrgController and update the literal
accordingly.

---

Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java`:
- Line 109: In OrgController, the `@DeleteMapping` on the delete member endpoint
is missing the leading slash; update the annotation on the method that handles
deleting members (the `@DeleteMapping`("{orgId}/members/{memberId}") declaration)
to include a leading "/" (i.e., `@DeleteMapping`("/{orgId}/members/{memberId}"))
so it matches the style of other mappings like `@PatchMapping`("/{orgId}") and
preserves path consistency and readability.

Comment on lines 44 to 49
List<OrgMember> orgMemberByUser = orgMemberRepository.findOrgMemberByUser(user);
for (OrgMember orgMember : orgMemberByUser) {
//OrgMember 내부 Organization 의 name 이 생성하려는 request 의 name 과 같으면
// OrgMember 내부 Organization 의 name 이 생성하려는 request 의 name 과 같으면
if (orgMember.getOrganization().getName().equals(request.name())) {
throw new OrgHandler(OrgErrorCode.ORG_NAME_DUPLICATE); //예외처리
throw new OrgHandler(OrgErrorCode.ORG_NAME_DUPLICATE); // 예외처리
}
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

🧩 Analysis chain

🏁 Script executed:

# OrgMember 엔티티 파일 위치 찾기
fd OrgMember.java --type f

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 167


🏁 Script executed:

# OrgServiceImpl 파일 위치 찾기 및 해당 메서드 확인
fd OrgServiceImpl.java --type f

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 168


🏁 Script executed:

# OrgMemberRepository 파일 위치
fd OrgMemberRepository.java --type f

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 181


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/entity/OrgMember.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 1342


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 8207


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 2743


createOrganization에서 N+1 쿼리 문제가 실제로 발생합니다.

OrgMemberorganization 필드가 FetchType.LAZY로 설정되어 있고, findOrgMemberByUser(user) 메서드가 JOIN FETCH 없이 단순 쿼리로 작동하기 때문입니다.

현재 코드 흐름:

  • 1번 쿼리: findOrgMemberByUser로 OrgMember 리스트 조회
  • N번 쿼리: 루프 내 orgMember.getOrganization().getName() 호출 시 각각 Organization 조회
  • 총 1 + N개 쿼리 발생 (N = 사용자가 속한 조직 수)

해결 방법:

  1. Repository 메서드에 JOIN FETCH 추가 (권장)

    `@Query`("SELECT om FROM OrgMember om " +
           "JOIN FETCH om.organization o " +
           "WHERE om.user = :user")
    List<OrgMember> findOrgMemberByUser(`@Param`("user") User user);
  2. 이름 중복 검사 전용 쿼리 추가

    `@Query`("SELECT CASE WHEN COUNT(o) > 0 THEN true ELSE false END " +
           "FROM OrgMember om " +
           "JOIN om.organization o " +
           "WHERE om.user = :user AND o.name = :name")
    boolean existsByUserAndOrganizationName(`@Param`("user") User user, `@Param`("name") String name);

중복 검사만 필요하다면 두 번째 방식이 더 효율적입니다.

🤖 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 44 - 49, The createOrganization method in OrgServiceImpl triggers
an N+1 query because orgMemberRepository.findOrgMemberByUser(user) returns
OrgMember entities whose organization is LAZY and you call
orgMember.getOrganization().getName() in a loop; fix this by updating the
repository: either change findOrgMemberByUser to fetch organizations with a JOIN
FETCH so Organization is loaded with OrgMember, or (preferred for simple
duplicate checks) add a repository method existsByUserAndOrganizationName(user,
name) that runs a single query to test name existence and call that from
createOrganization instead of iterating orgMemberRepository.findOrgMemberByUser;
update OrgServiceImpl.createOrganization to use the new repository method (or
the JOIN FETCH variant) and remove the loop-based name checks to eliminate the
N+1 queries.

Comment on lines +150 to +155
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);
}
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

요청자가 조직에 없을 때 ORG_MEMBER_NOT_FOUND(404)를 반환하면 HTTP 의미론(semantics)과 맞지 않습니다.

현재 로직은:

  • 요청자가 조직에 없으면 → ORG_MEMBER_NOT_FOUND (404)
  • 요청자가 있지만 ADMIN이 아니면 → ORG_MEMBER_FORBIDDEN (403)

두 경우 모두 "권한 없음" 상황인데, 전자는 404, 후자는 403이 반환됩니다. 조직에 없는 외부인이 멤버 추방을 시도할 때 "해당 멤버가 조직에 존재하지 않습니다"(404)를 받으면 조직 멤버십 여부가 노출되는 정보 노출 문제도 있습니다. 두 경우 모두 ORG_MEMBER_FORBIDDEN(403)으로 통일하는 것이 더 안전합니다.

🛠️ 수정 제안
-        OrgMember requester = orgMemberRepository.findByUserIdAndOrgId(userId, orgId)
-                .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND));
+        OrgMember requester = orgMemberRepository.findByUserIdAndOrgId(userId, orgId)
+                .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN));

         if (requester.getRole() != OrgRole.ADMIN) {
             throw new OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN);
         }
🤖 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 150 - 155, Change the behavior in OrgServiceImpl where you fetch
the requester via orgMemberRepository.findByUserIdAndOrgId(...) so that both
"not a member" and "not ADMIN" cases throw OrgHandler with
OrgErrorCode.ORG_MEMBER_FORBIDDEN instead of returning ORG_MEMBER_NOT_FOUND;
specifically replace the orElseThrow(...) that currently uses
ORG_MEMBER_NOT_FOUND to use ORG_MEMBER_FORBIDDEN, leaving the subsequent role
check throwing ORG_MEMBER_FORBIDDEN intact so both branches consistently return
403.

Comment on lines +22 to +26
@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);
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

🧩 Analysis chain

🏁 Script executed:

# OrgMemberRepository 전체 내용 조회
find . -type f -name "OrgMemberRepository.java" | head -1 | xargs cat -n

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 2743


🏁 Script executed:

# User 엔티티 찾기 및 status 필드 확인
find . -type f -name "User.java" | grep -E "domains|entity" | head -3

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 156


🏁 Script executed:

# UserStatus enum 찾기
find . -type f -name "UserStatus.java" | head -1

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 159


🏁 Script executed:

# User.java에서 status 필드 및 `@Enumerated` 설정 확인
cat -n ./src/main/java/com/whereyouad/WhereYouAd/domains/user/persistence/entity/User.java | grep -A 5 -B 5 "status"

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 610


🏁 Script executed:

# UserStatus enum 전체 확인
cat -n ./src/main/java/com/whereyouad/WhereYouAd/domains/user/domain/constant/UserStatus.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 230


🏁 Script executed:

# removeMemberFromOrg 메서드 찾기
rg "removeMemberFromOrg|findByUserIdAndOrgId" -A 5 -B 2

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 5882


일관성 있게 매개변수로 UserStatus를 전달하세요.

현재 User 엔티티의 status는 @Enumerated(EnumType.STRING) 으로 설정되어 있어서, 'ACTIVE' 문자열 리터럴도 기술적으로는 정상 작동합니다. 다만 같은 파일의 findByOrganizationIdWithCursor(라인 36), countByOrganizationIdAndUserStatus(라인 50) 메서드들은 모두 @Param("status") UserStatus status 로 매개변수화하여 타입 안전하게 처리하고 있습니다.

이 메서드도 동일한 패턴을 따르면:

  • 쿼리 문자열에 하드코딩된 상태값을 제거해 유지보수성 향상
  • 향후 SUSPENDED나 DELETED 상태를 조회해야 할 필요가 생길 때 유연하게 대응
  • 코드 일관성 개선

호출부(OrgServiceImpl)에서는 UserStatus.ACTIVE를 명시적으로 전달하면 됩니다.

-    `@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 = :status")
+    Optional<OrgMember> findByUserIdAndOrgId(
+            `@Param`("userId") Long userId,
+            `@Param`("orgId") Long orgId,
+            `@Param`("status") UserStatus status);
🤖 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 22 - 26, The findByUserIdAndOrgId method in OrgMemberRepository
currently hardcodes 'ACTIVE' in the JPQL; change the query to use a status
parameter (e.g. "AND om.user.status = :status"), update the method signature to
accept `@Param`("status") UserStatus status, and ensure callers (like
OrgServiceImpl) pass UserStatus.ACTIVE; this makes the method type-safe and
consistent with findByOrganizationIdWithCursor and
countByOrganizationIdAndUserStatus.

Comment on lines +104 to +108
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "403", description = "권한이 부족한 경우(요청을 보낸 유저의 권한이 ADMIN이 아닌 경우)"),
@ApiResponse(responseCode = "404", description = "해당 id의 데이터 존재 X")
})
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

Swagger 문서에 400 오류 케이스가 누락되었습니다.

OrgServiceImpl.removeMemberFromOrg에서 실제로 400(BAD_REQUEST) 상태코드를 반환하는 케이스가 2가지(ORG_400_2: 자기 자신 추방 불가, ORG_400_3: ADMIN 추방 불가) 있지만, @ApiResponses에 문서화되어 있지 않습니다. Swagger UI를 사용하는 클라이언트 개발자가 이 오류를 인지할 수 없습니다.

📄 수정 제안
     `@ApiResponses`({
             `@ApiResponse`(responseCode = "200", description = "성공"),
+            `@ApiResponse`(responseCode = "400_2", description = "자기 자신을 추방하려는 경우"),
+            `@ApiResponse`(responseCode = "400_3", description = "ADMIN 멤버를 추방하려는 경우"),
             `@ApiResponse`(responseCode = "403", description = "권한이 부족한 경우(요청을 보낸 유저의 권한이 ADMIN이 아닌 경우)"),
             `@ApiResponse`(responseCode = "404", description = "해당 id의 데이터 존재 X")
     })
📝 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
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "403", description = "권한이 부족한 경우(요청을 보낸 유저의 권한이 ADMIN이 아닌 경우)"),
@ApiResponse(responseCode = "404", description = "해당 id의 데이터 존재 X")
})
`@ApiResponses`({
`@ApiResponse`(responseCode = "200", description = "성공"),
`@ApiResponse`(responseCode = "400", description = "요청이 잘못된 경우(자기 자신 추방 불가, ADMIN 멤버 추방 불가)"),
`@ApiResponse`(responseCode = "403", description = "권한이 부족한 경우(요청을 보낸 유저의 권한이 ADMIN이 아닌 경우)"),
`@ApiResponse`(responseCode = "404", description = "해당 id의 데이터 존재 X")
})
🤖 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`
around lines 104 - 108, The Swagger docs for OrgController are missing 400
responses for cases thrown by OrgServiceImpl.removeMemberFromOrg; update the
`@ApiResponses` array in OrgControllerDocs (where `@ApiResponses/`@ApiResponse are
declared) to include two `@ApiResponse` entries with responseCode = "400" and
descriptions for ORG_400_2 ("cannot remove yourself") and ORG_400_3 ("cannot
remove ADMIN"), so clients see the BAD_REQUEST cases that removeMemberFromOrg
can return.

@PathVariable Long memberId
) {
orgService.removeMemberFromOrg(userId, orgId, memberId);
return ResponseEntity.ok(DataResponse.from("해당 맴버가 조직에서 제외되었습니다."));
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

사용자 응답 메시지에 오타가 있습니다: "맴버" → "멤버"

"해당 맴버가 조직에서 제외되었습니다." 에서 "맴버"는 잘못된 표기입니다. PR의 다른 파일들(OrgControllerDocs.java)과 일관된 "멤버"로 수정해야 합니다.

✏️ 수정 제안
-        return ResponseEntity.ok(DataResponse.from("해당 맴버가 조직에서 제외되었습니다."));
+        return ResponseEntity.ok(DataResponse.from("해당 멤버가 조직에서 제외되었습니다."));
📝 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
return ResponseEntity.ok(DataResponse.from("해당 맴버가 조직에서 제외되었습니다."));
return ResponseEntity.ok(DataResponse.from("해당 멤버가 조직에서 제외되었습니다."));
🤖 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`
at line 116, In OrgController (method returning
ResponseEntity.ok(DataResponse.from(...))) fix the Korean typo in the response
string by changing "해당 맴버가 조직에서 제외되었습니다." to "해당 멤버가 조직에서 제외되었습니다." so it
matches other docs (e.g., OrgControllerDocs) and maintains consistent messaging;
locate the DataResponse.from(...) call in OrgController and update the literal
accordingly.

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 끼리는 추방이 안되도록 하는 건 제가 보기엔 괜찮아 보이긴 하는데, 그러면 이후에 조직 내 다른 회원 권한을 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.

P4: 고생하셨습니다! 우선은 ADMIN끼리는 추방할 수 없게 두고, 전체 회의 때 어떻게 하면 좋을지 논의해봐도 괜찮을 것 같아요!

# Conflicts:
#	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/persistence/repository/OrgMemberRepository.java
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.

🧹 Nitpick comments (2)
src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java (1)

21-23: 주석의 오타 수정 필요 ("맴버" → "멤버")

메서드 시그니처 자체는 깔끔하게 잘 작성되었어요 👍. 다만 22번째 줄 주석에 오타가 있습니다. "맴버"는 "멤버"의 잘못된 표기입니다.

✏️ 오타 수정 제안
-    // orgId 조직에서 memberId에 해당하는 맴버 제거
+    // orgId 조직에서 memberId에 해당하는 멤버 제거
     void removeMemberFromOrg(Long userId, Long orgId, Long memberId);
🤖 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/OrgService.java`
around lines 21 - 23, Update the Korean comment above the method
removeMemberFromOrg(Long userId, Long orgId, Long memberId) to correct the typo:
change "맴버" to "멤버" so the comment reads "orgId 조직에서 memberId에 해당하는 멤버 제거" (or
equivalent correct Korean phrasing) while leaving the method signature
unchanged.
src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java (1)

11-22: 새 에러 코드 구성 훌륭해요! 👍

ORG_CANNOT_KICK_SELF → 400, ORG_MEMBER_FORBIDDEN → 403, ORG_MEMBER_NOT_FOUND → 404 로 각 실패 케이스를 명확하게 구분한 점이 좋습니다. PR 스크린샷과 에러 코드(ORG_400_2, ORG_400_3)도 정확하게 일치하네요.

다만 주석 스타일에 사소한 불일치가 있습니다. 이번에 수정된 // 400, // 403, // 404는 슬래시 뒤에 공백이 추가되었는데, 24번째와 28번째 줄의 //409, //410에는 아직 공백이 없습니다.

✏️ 주석 스타일 일관성 수정 제안
-    //409
+    // 409
     ORG_ALREADY_ACTIVE(HttpStatus.CONFLICT, "ORG_409_1", "해당 조직은 이미 활성화 상태 입니다."),

-    //410
+    // 410
     ORG_SOFT_DELETED(HttpStatus.GONE, "ORG_410_1", "해당 조직은 삭제된 조직입니다.(Soft Delete)"),
🤖 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/exception/code/OrgErrorCode.java`
around lines 11 - 22, Normalize the inline comment spacing to match the existing
style by adding a single space after the slashes for the `//409` and `//410`
comment markers so they read `// 409` and `// 410`; update the two comment
tokens near the ORG error block (the lines currently showing `//409` and
`//410`) so all section comments (`// 400`, `// 403`, `// 404`, `// 409`, `//
410`) use the same `// ` format.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java`:
- Around line 22-26: The query in findByUserIdAndOrgId currently hardcodes the
user status as the string 'ACTIVE'; change the method to accept a UserStatus
parameter (e.g., `@Param`("status") UserStatus status) and update the JPQL to use
"AND om.user.status = :status" so it becomes type-safe and consistent with
findByOrganizationIdWithCursor and countByOrganizationIdAndUserStatus; update
the method signature accordingly to take the status param and return
Optional<OrgMember>.

---

Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgService.java`:
- Around line 21-23: Update the Korean comment above the method
removeMemberFromOrg(Long userId, Long orgId, Long memberId) to correct the typo:
change "맴버" to "멤버" so the comment reads "orgId 조직에서 memberId에 해당하는 멤버 제거" (or
equivalent correct Korean phrasing) while leaving the method signature
unchanged.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java`:
- Around line 11-22: Normalize the inline comment spacing to match the existing
style by adding a single space after the slashes for the `//409` and `//410`
comment markers so they read `// 409` and `// 410`; update the two comment
tokens near the ORG error block (the lines currently showing `//409` and
`//410`) so all section comments (`// 400`, `// 403`, `// 404`, `// 409`, `//
410`) use the same `// ` format.

@kingmingyu kingmingyu merged commit ef1d9d3 into develop Feb 19, 2026
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 19, 2026
5 tasks
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