Conversation
…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
Walkthrough조직 멤버의 역할 변경 기능을 추가합니다. PATCH 요청으로 조직·요청자·대상 멤버를 검증(권한·존재·마지막 ADMIN 검사)한 뒤 엔티티 역할을 갱신하고 변경된 멤버 DTO를 반환합니다. Changes
Sequence DiagramsequenceDiagram
participant Client as "Client"
participant Controller as "OrgController"
participant Service as "OrgServiceImpl"
participant Repo as "OrgMemberRepository"
participant Entity as "OrgMember"
Client->>Controller: PATCH /api/org/members/{orgId}/{memberId}\nBody: UpdateRole{orgRole}
Controller->>Service: updateOrgMembersRole(userId, orgId, memberId, dto)
Service->>Repo: findByOrgIdAndId(orgId) / find Organization
Repo-->>Service: Organization
Service->>Repo: findByUserIdAndOrgId(userId, orgId)
Repo-->>Service: OrgMember (requester)
Note over Service: 요청자 역할 검사 (ADMIN 여부)
Service->>Repo: findByOrgIdAndId(orgId, memberId)
Repo-->>Service: OrgMember (target)
Note over Service: 마지막 ADMIN 검사 (countByOrganizationIdAndRole)
Service->>Entity: orgMember.updateRole(newRole)
Entity-->>Service: 변경 반영 (dirty-checking)
Service->>Service: OrgConverter.toOrgMemberDTO(updated)
Service-->>Controller: OrgMemberDTO
Controller-->>Client: 200 DataResponse<OrgMemberDTO>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.ACTIVEenum의 이름이 변경되면 컴파일 에러 없이 런타임에서 쿼리가 잘못 동작할 수 있습니다.파라미터 방식으로 변경하면 타입 안전성이 확보됩니다:
♻️ 파라미터 바인딩으로 변경 제안
- `@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호출 시 추가 쿼리가 발생합니다.
findByUserIdAndOrgIdJPQL은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.
| // 1. 조직 존재 여부 확인 | ||
| Organization organization = orgRepository.findById(orgId) | ||
| .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_NOT_FOUND)); |
There was a problem hiding this comment.
소프트 삭제된 조직에 대한 권한 변경이 가능합니다 — 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: 전체 멤버 수)"), |
There was a problem hiding this comment.
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.
| @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의 권한 변경)"), |
There was a problem hiding this comment.
잘못된 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.
| @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 |
There was a problem hiding this comment.
@RequestBody에 @Valid가 누락되었습니다 — @NotNull 검증이 동작하지 않습니다.
AI 요약에 따르면 OrgRequest.UpdateRole의 orgRole 필드에 @NotNull이 선언되어 있습니다. 그런데 @Valid가 없으면 Spring은 Bean Validation을 실행하지 않아서, orgRole이 null인 요청이 그대로 서비스 레이어까지 내려갑니다. 이는 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).
src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java
Show resolved
Hide resolved
| @AuthenticationPrincipal(expression = "userId") Long userId, | ||
| @PathVariable Long orgId, | ||
| @PathVariable Long memberId, | ||
| @RequestBody OrgRequest.UpdateRole dto |
There was a problem hiding this comment.
P3: Controller 메서드에서 OrgRequest.UpdateRole 필드에 Valid 어노테이션 달아서 NotNull 검사 적용되게 해주면 좋을 것 같아요!
There was a problem hiding this comment.
수정했습니다! 꼼꼼하게 봐주셔서 감사합니다!
ojy0903
left a comment
There was a problem hiding this comment.
P4: 고생하셨어요! 제가 보기엔 ADMIN 인 사용자를 MEMBER 로 바꾸지 못하면 문제가 생길 수도 있을 것 같은데, 아예 OrgRole 에 CREATOR 같이 최고 권한 역할을 만드는게 좋을까요? 아니면 지금처럼 ADMIN 은 MEMBER 로 바꾸지 못하도록 하되 화면 UI 에서 사용자에게 경고를 주는 방식으로 하는 게 맞을까요? 지민님 의견도 들어봐야 될 듯 합니다...
저도 뭔가 계속 새로운 권한을 만들면 끝이 없을 것 같아서 고민 중이었습니다..! 최고 권한 역할도 좋습니다 뭔가 ADMIN도 강등할 수 있는 방법이 필요할 것 같긴 합니다! |
There was a problem hiding this comment.
P3: 고생하셨습니다! 제 생각에도 ADMIN이 MEMBER 권한으로 변경될 수 없다면 문제가 있을 것 같습니다.. 해당 조직 내 ADMIN이 두 명 이상 존재한다면 ADMIN -> MEMBER를 가능하게 하는 건 어떨까요?
만약 추가로 더 구현한다면, 조직 내 ADMIN이 오직 한 명이면 관리자를 다른 이로 임명하게끔 구현해도 될 듯 합니다! (+ ADMIN 권한인 유저 삭제(추방)하는 것도 비슷하게 고민해볼 수 있을 것 같습니다..)
|
P3: 지민님 제시하신 것처럼 조직 내에 ADMIN 이 2명 이상이면 강등 가능하게 하는 건 어떨까요? 생각해보니 OrgRole 에 새로 역할 추가를 하면 다른 메서드에서도 수정해야할 점이 많이 생길 수 있을 것 같아서... 이렇게 진행하고 역할 세분화는 2차 MVP 에 진행해보는거 어떨까요 |
# Conflicts: # src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java # 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/presentation/OrgController.java # src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java (1)
29-34:UpdateRole레코드 깔끔하게 잘 작성했네요! 👍Java 17
record문법과@NotNull,@Schema의 조합이 적절합니다.orgRole이null로 들어오는 케이스를 차단하고,OrgRole타입 자체로 잘못된 enum 문자열도 Jackson이 400 에러로 처리하므로 입력 방어가 이중으로 잘 되어 있습니다.한 가지 소소한 팁:
UpdateRole레코드 끝(}) 다음에 빈 줄이 없어서Invite레코드와 붙어 보입니다. 다른 레코드들처럼 구분을 위해 빈 줄 하나만 추가하면 가독성이 올라가요.✨ 선택적 개선 제안
) {} + public record Invite(🤖 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/application/dto/request/OrgRequest.java` around lines 29 - 34, Add a single blank line after the closing brace of the UpdateRole record so it is visually separated from the next Invite record; edit around the UpdateRole and Invite record declarations (the UpdateRole record closing "}" and the subsequent Invite record) to insert one empty line to improve readability and match surrounding record spacing.src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java (2)
129-138: [운영 설계 논의] ADMIN의 영구적 고착 문제 — 어떻게 처리할 건가요?PR 토론에서 이미 언급된 부분이지만, 코드 레벨에서도 짚어드립니다.
현재 구현은 ADMIN → MEMBER 다운그레이드를 전면 차단합니다. 이 경우 발생하는 실제 문제:
- 조직의 유일한 ADMIN이 탈퇴하고 싶어도 스스로 MEMBER로 바꾸거나 다른 사람에게 권한 이양 후 다운그레이드할 방법이 없음
- 실수로 권한을 부여한 경우 되돌릴 수 없어 관리 운영이 경직됨
권장 접근법 (ojy0903님 제안과 일치):
다운그레이드 허용 조건: org 내 ADMIN 수 >= 2 → 마지막 ADMIN은 보호, 여러 ADMIN 중 한 명은 다운그레이드 가능서비스 레이어에서 아래처럼 처리할 수 있습니다:
// 예시 로직 long adminCount = orgMemberRepository.countByOrganizationAndRole(org, OrgRole.ADMIN); if (adminCount <= 1 && targetMember.getRole() == OrgRole.ADMIN) { throw new OrgException(OrgErrorCode.ORG_LAST_ADMIN_CANNOT_DEMOTE); }현재 MVP 범위에서 새 에러코드(
ORG_LAST_ADMIN_CANNOT_DEMOTE) 하나만 추가하면 CREATOR 롤 도입 없이도 해결됩니다. CREATOR 롤은 2차 MVP에서 고려하는 것이 좋겠습니다.🤖 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 controller method updateOrgMembersRole calls orgService.updateOrgMembersRole but the service currently blocks all ADMIN→MEMBER demotions; change the service logic to allow demotion only when the organization has at least 2 admins: in the service method check orgMemberRepository.countByOrganizationAndRole(org, OrgRole.ADMIN) and if the count <= 1 and the targetMember.getRole() == OrgRole.ADMIN throw a new OrgException(OrgErrorCode.ORG_LAST_ADMIN_CANNOT_DEMOTE); add the new OrgErrorCode.ORG_LAST_ADMIN_CANNOT_DEMOTE constant to your error code enum so callers can surface a clear error for attempts to demote the last admin.
129-138:@Valid추가 확인! 이전 리뷰에서 지적된 NPE 위험이 해결되었습니다. 🎉Line 134에서
@RequestBody@ValidOrgRequest.UpdateRole dto로 잘 수정되었습니다.orgRole: null이 서비스 레이어까지 내려가던 위험이 차단되었네요.한 가지 선택적 개선 사항이 있어요 — URL 경로 설계 불일치입니다.
현재 같은 "조직 멤버" 리소스를 다루는 두 엔드포인트의 경로가 서로 다릅니다:
메서드 현재 경로 (매핑 + base) DELETE(Line 119)/api/org/{orgId}/members/{memberId}PATCH (신규) /api/org/members/{orgId}/{memberId}← 다름!REST 컨벤션에서 동일 리소스는 동일한 경로 구조를 가져야 합니다.
DELETE의 패턴(/{orgId}/members/{memberId})을 따르는 것이 더 일관적이고, Swagger 문서나 클라이언트 SDK 자동 생성 시에도 혼란이 없습니다.♻️ URL 일관성 개선 제안
- `@PatchMapping`("/members/{orgId}/{memberId}") + `@PatchMapping`("/{orgId}/members/{memberId}") public ResponseEntity<DataResponse<OrgResponse.OrgMemberDTO>> updateOrgMembersRole(
OrgControllerDocs.java의@PathVariable순서 및@Operation설명도 함께 업데이트하세요.🤖 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, Change the PATCH mapping in OrgController so its URL matches the DELETE pattern for the same resource: update the `@PatchMapping` on updateOrgMembersRole to use "/{orgId}/members/{memberId}" (instead of "/members/{orgId}/{memberId}"), keep the method signature (updateOrgMembersRole(..., `@PathVariable` Long orgId, `@PathVariable` Long memberId, `@RequestBody` `@Valid` OrgRequest.UpdateRole dto)) and then update the corresponding docs class (OrgControllerDocs) to align `@PathVariable` order and the `@Operation` description to reflect the corrected path; ensure no other callers or tests expect the old path.
🤖 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/persistence/repository/OrgMemberRepository.java`:
- Around line 43-48: The query findByUserIdAndOrgId causes N+1 because om.user
is LAZY; modify the repository query to JOIN FETCH the user (e.g., "SELECT om
FROM OrgMember om JOIN FETCH om.user u WHERE om.user.id = :userId AND
om.organization.id = :orgId AND u.status = :status") and change the method
signature to accept a UserStatus status parameter; then update all callers
(e.g., updateOrgMembersRole and removeMemberFromOrg) to pass UserStatus.ACTIVE
so OrgConverter.toOrgMemberDTO can access orgMember.getUser() without triggering
an extra select, matching the pattern used in findByOrganizationIdWithCursor.
---
Duplicate comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/docs/OrgControllerDocs.java`:
- Line 152: The docs interface OrgControllerDocs is missing `@Valid` on
`@RequestBody` parameters; add `@Valid` to the `@RequestBody` parameter for the
updateRole method (parameter type OrgRequest.UpdateRole) and likewise ensure the
createOrganization and modifyOrganization methods in OrgControllerDocs also
annotate their `@RequestBody` parameters with `@Valid` so the docs interface matches
the runtime controller signatures.
- Around line 143-144: Update the incorrect ApiResponse annotations in
OrgControllerDocs: replace the `@ApiResponse`(responseCode = "200", description =
"성공 (totalCount: 전체 멤버 수)") with a clear success description reflecting the
actual response payload for the endpoint, and change the mistaken
`@ApiResponse`(responseCode = "401", description = "잘못된 요청을 보낸 경우(ADMIN의 권한 변경)")
to the correct response code (e.g., responseCode = "400" or "403" as
appropriate) and update its description to accurately reflect the error
condition; modify the two `@ApiResponse` annotations shown (the
`@ApiResponse`(responseCode = "200", ...) and the `@ApiResponse`(responseCode =
"401", ...)) in OrgControllerDocs.java accordingly.
---
Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/application/dto/request/OrgRequest.java`:
- Around line 29-34: Add a single blank line after the closing brace of the
UpdateRole record so it is visually separated from the next Invite record; edit
around the UpdateRole and Invite record declarations (the UpdateRole record
closing "}" and the subsequent Invite record) to insert one empty line to
improve readability and match surrounding record spacing.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/presentation/OrgController.java`:
- Around line 129-138: The controller method updateOrgMembersRole calls
orgService.updateOrgMembersRole but the service currently blocks all
ADMIN→MEMBER demotions; change the service logic to allow demotion only when the
organization has at least 2 admins: in the service method check
orgMemberRepository.countByOrganizationAndRole(org, OrgRole.ADMIN) and if the
count <= 1 and the targetMember.getRole() == OrgRole.ADMIN throw a new
OrgException(OrgErrorCode.ORG_LAST_ADMIN_CANNOT_DEMOTE); add the new
OrgErrorCode.ORG_LAST_ADMIN_CANNOT_DEMOTE constant to your error code enum so
callers can surface a clear error for attempts to demote the last admin.
- Around line 129-138: Change the PATCH mapping in OrgController so its URL
matches the DELETE pattern for the same resource: update the `@PatchMapping` on
updateOrgMembersRole to use "/{orgId}/members/{memberId}" (instead of
"/members/{orgId}/{memberId}"), keep the method signature
(updateOrgMembersRole(..., `@PathVariable` Long orgId, `@PathVariable` Long
memberId, `@RequestBody` `@Valid` OrgRequest.UpdateRole dto)) and then update the
corresponding docs class (OrgControllerDocs) to align `@PathVariable` order and
the `@Operation` description to reflect the corrected path; ensure no other
callers or tests expect the old path.
| // 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); |
There was a problem hiding this comment.
🧩 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
JOIN FETCH om.user 누락 — 실제 N+1 쿼리 발생
검증 결과, findByUserIdAndOrgId를 호출한 후 user 필드를 접근하는 메서드에서 실제로 N+1 쿼리가 발생합니다.
문제 시나리오: updateOrgMembersRole 메서드를 보면:
- 216번 줄에서
findByUserIdAndOrgId(memberId, orgId)호출 → OrgMember 반환 (user는 LAZY) - 227번 줄에서
OrgConverter.toOrgMemberDTO(orgMember)호출 - OrgConverter 내부에서
orgMember.getUser().getName(),.getEmail(),.getProfileImageUrl()접근 - 결과: 추가 SELECT 쿼리 1개 발생 (N+1)
클래스 레벨의 @Transactional이 있어서 LazyInitializationException은 피하지만, 불필요한 쿼리가 추가로 실행됩니다.
같은 레포지토리의 findByOrganizationIdWithCursor(Line 31-35)는 이미 JOIN FETCH m.user u를 명시하고 있으므로, 일관성 있게 이 메서드도 수정하는 것이 좋습니다.
추가로 om.user.status = 'ACTIVE'는 기술적으로 안전하지만(@Enumerated(EnumType.STRING) 적용됨), findByOrganizationIdWithCursor처럼 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 " +
+ "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
+);호출부(updateOrgMembersRole, removeMemberFromOrg)에서 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, The query findByUserIdAndOrgId causes N+1 because om.user
is LAZY; modify the repository query to JOIN FETCH the user (e.g., "SELECT om
FROM OrgMember om JOIN FETCH om.user u WHERE om.user.id = :userId AND
om.organization.id = :orgId AND u.status = :status") and change the method
signature to accept a UserStatus status parameter; then update all callers
(e.g., updateOrgMembersRole and removeMemberFromOrg) to pass UserStatus.ACTIVE
so OrgConverter.toOrgMemberDTO can access orgMember.getUser() without triggering
an extra select, matching the pattern used in findByOrganizationIdWithCursor.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java (1)
200-238: 동일 역할로의 변경 요청에 대한 처리가 없습니다.현재 코드는 대상 멤버가 이미 요청된 역할을 갖고 있어도 (예: MEMBER → MEMBER)
updateRole()을 호출하고 정상 응답을 반환합니다. 기능상 심각한 문제는 아니지만, 불필요한 dirty checking과 UPDATE 쿼리가 발생할 수 있습니다.간단한 early return으로 이를 방지할 수 있습니다:
♻️ 제안
OrgMember orgMember = orgMemberRepository.findByUserIdAndOrgId(memberId, orgId) .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND)); + // 동일 역할 변경 요청은 바로 반환 + if (orgMember.getRole() == dto.orgRole()) { + return OrgConverter.toOrgMemberDTO(orgMember); + } + // 4. ADMIN -> MEMBER 강등 시: 조직 내 ADMIN이 2명 이상이어야만 허용🤖 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 200 - 238, The method updateOrgMembersRole performs unnecessary updates when the target member already has the requested role; add an early return to skip work when orgMember.getRole() equals dto.orgRole(): before performing the demotion check and before calling orgMember.updateRole(dto.orgRole()), compare current role and if identical return OrgConverter.toOrgMemberDTO(orgMember) immediately so no dirty-checking/UPDATE occurs; ensure this check appears in updateOrgMembersRole prior to the isDemoting logic that uses dto.orgRole() and orgMember.getRole().
🤖 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 224-232: The demotion guard in OrgServiceImpl incorrectly uses
adminCount <= 2 which blocks demotion when there are exactly 2 admins; change
the check in the block that computes isDemoting (where orgMember.getRole() ==
OrgRole.ADMIN && dto.orgRole() == OrgRole.MEMBER) to use adminCount < 2 by
calling orgMemberRepository.countByOrganizationIdAndRole(orgId, OrgRole.ADMIN)
and throwing new OrgHandler(OrgErrorCode.ORG_LAST_ADMIN) only when adminCount <
2 so demotion is allowed when there are 2 admins but blocked when fewer than 2.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java`:
- Around line 15-16: The enum constant ORG_LAST_ADMIN in OrgErrorCode has a
misleading message; update its message string to clearly explain the failure
reason (e.g., "마지막 ADMIN은 강등할 수 없습니다." or "조직에 최소 1명의 ADMIN이 필요하므로 강등할 수 없습니다.")
so API clients understand why the operation was rejected; locate the
ORG_LAST_ADMIN entry in OrgErrorCode and replace the current message "ADMIN은 2명
이상입니다." with the clearer failure-oriented message.
---
Duplicate comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`:
- Around line 208-210: The code in OrgServiceImpl retrieves Organization via
orgRepository.findById but does not guard against soft-deleted entities,
allowing actions on organizations with OrgStatus.DELETED; update the logic after
Organization organization = orgRepository.findById(orgId).orElseThrow(...) to
check organization.getStatus() (or organization.status) against
OrgStatus.DELETED and throw the same OrgHandler(OrgErrorCode.ORG_NOT_FOUND) (or
a suitable error) when deleted—follow the same pattern used in
getOrganizationDetail to ensure deleted orgs are treated as not found before
proceeding with role changes.
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/persistence/repository/OrgMemberRepository.java`:
- Around line 44-49: The query in OrgMemberRepository.findByUserIdAndOrgId
should eagerly fetch the associated user to avoid N+1 when
OrgConverter.toOrgMemberDTO accesses
om.getUser().getName()/getEmail()/getProfileImageUrl(); change the JPQL to
include "JOIN FETCH om.user" consistent with findByOrganizationIdWithCursor and
replace the hard-coded "om.user.status = 'ACTIVE'" literal with a bound
parameter (e.g., :userStatus) so callers pass the active status constant; update
method signature to accept the status parameter accordingly.
---
Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`:
- Around line 200-238: The method updateOrgMembersRole performs unnecessary
updates when the target member already has the requested role; add an early
return to skip work when orgMember.getRole() equals dto.orgRole(): before
performing the demotion check and before calling
orgMember.updateRole(dto.orgRole()), compare current role and if identical
return OrgConverter.toOrgMemberDTO(orgMember) immediately so no
dirty-checking/UPDATE occurs; ensure this check appears in updateOrgMembersRole
prior to the isDemoting logic that uses dto.orgRole() and orgMember.getRole().
src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java
Show resolved
Hide resolved
src/main/java/com/whereyouad/WhereYouAd/domains/organization/exception/code/OrgErrorCode.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java (1)
234-238:toOrgMemberDTO에서User엔티티 Lazy Loading이 발생할 수 있습니다.
OrgConverter.toOrgMemberDTO(orgMember)는 내부적으로orgMember.getUser().getName(),getEmail(),getProfileImageUrl()을 호출합니다 (OrgConverter Line 77~81 참고).OrgMember의User필드가FetchType.LAZY라면, 이 시점에 추가 SELECT 쿼리가 발생합니다.현재는 단건 조회이므로 심각한 성능 이슈는 아니지만,
findByUserIdAndOrgId를JOIN FETCH로 User까지 함께 가져오면 쿼리 하나를 절약할 수 있습니다.#!/bin/bash # Description: Check OrgMember entity's User field fetch type echo "=== OrgMember entity - User relationship ===" rg -n -B 2 -A 5 "User user" --type=java -g '*OrgMember.java' echo "" echo "=== findByUserIdAndOrgId query definition ===" rg -n -B 3 -A 5 "findByUserIdAndOrgId" --type=java -g '*Repository*'🤖 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 234 - 238, OrgConverter.toOrgMemberDTO(orgMember) may trigger lazy-loading of orgMember.getUser(), causing an extra SELECT; update the retrieval so User is fetched with the OrgMember to avoid that. Modify the repository method used by OrgServiceImpl (the findByUserIdAndOrgId query) to include a JOIN FETCH for the User association (or add a new repository method that does so) and use that repository call before calling OrgConverter.toOrgMemberDTO(orgMember); alternatively, explicitly initialize orgMember.getUser() (e.g., via repository join fetch or EntityManager) prior to invoking OrgConverter.toOrgMemberDTO to prevent the additional lazy load.
🤖 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 203-206: The parameter name memberId is misleading (it is actually
a User ID) — update the parameter to a clear name (e.g., targetUserId) in
OrgServiceImpl methods (including the method that contains the self-check block
and removeMemberFromOrg), update any PathVariable/endpoint mappings to
/members/{orgId}/{userId} or to use {targetUserId}, and change all internal
usages (calls to findByUserIdAndOrgId(memberId, orgId) →
findByUserIdAndOrgId(targetUserId, orgId) and related lookups) so variable names
and API path match the actual semantics; ensure exception messages and logging
(OrgHandler/OrgErrorCode usage) reference the new parameter name for clarity.
---
Duplicate comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`:
- Around line 208-210: After retrieving Organization via orgRepository.findById
in OrgServiceImpl (the block using
orgRepository.findById(orgId).orElseThrow(...)), add the same soft-delete check
used in getOrganizationDetail: if organization.getStatus() == OrgStatus.DELETED
then throw new OrgHandler(OrgErrorCode.ORG_NOT_FOUND) (or the appropriate
OrgErrorCode used for deleted orgs) so soft-deleted organizations cannot be
acted on; place this check immediately after the organization assignment before
any member-role modification logic.
---
Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/organization/domain/service/OrgServiceImpl.java`:
- Around line 234-238: OrgConverter.toOrgMemberDTO(orgMember) may trigger
lazy-loading of orgMember.getUser(), causing an extra SELECT; update the
retrieval so User is fetched with the OrgMember to avoid that. Modify the
repository method used by OrgServiceImpl (the findByUserIdAndOrgId query) to
include a JOIN FETCH for the User association (or add a new repository method
that does so) and use that repository call before calling
OrgConverter.toOrgMemberDTO(orgMember); alternatively, explicitly initialize
orgMember.getUser() (e.g., via repository join fetch or EntityManager) prior to
invoking OrgConverter.toOrgMemberDTO to prevent the additional lazy load.
| // 0. 본인 권한 변경 불가 | ||
| if (Objects.equals(userId, memberId)) { | ||
| throw new OrgHandler(OrgErrorCode.ORG_CANNOT_ROLE_CHANGE_SELF); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the controller and repository to verify the memberId parameter semantics
echo "=== OrgController - Check for updateOrgMembersRole or similar endpoints ==="
rg -n -A 15 "updateOrgMembersRole|changeMemberRole|role.*member" --type=java | grep -v "ServiceImpl" | grep -v ".class" | head -50
echo ""
echo "=== OrgController - Look for PATCH endpoints ==="
rg -n -B 3 -A 15 "@PatchMapping|@PutMapping" --type=java | grep -A 15 "members.*memberId\|member.*role" | head -60
echo ""
echo "=== Check OrgRequest class for UpdateRole DTO ==="
rg -n -B 2 -A 5 "class UpdateRole|record UpdateRole" --type=java
echo ""
echo "=== Repository - Check findByUserIdAndOrgId signature ==="
rg -n -B 2 -A 3 "findByUserIdAndOrgId" --type=java
echo ""
echo "=== OrgServiceImpl - Check the full method including parameter and usage ==="
rg -n -B 5 -A 15 "updateOrgMembersRole|changeMemberRole" --type=java | grep -A 15 "ServiceImpl"Repository: WhereYouAd/WhereYouAd-Backend
Length of output: 18113
memberId 파라미터명이 API 경로와 실제 동작이 일치하지 않습니다. 명확히 수정이 필요합니다.
API 경로 /members/{orgId}/{memberId}에서 memberId는 OrgMember의 PK를 의미하는 것처럼 보이지만, 실제로는 User의 ID입니다.
증거:
- Line 221의
findByUserIdAndOrgId(memberId, orgId)에서memberId를userId파라미터 위치에 전달 - Repository 메서드 시그니처:
findByUserIdAndOrgId(@param("userId") Long userId,@param("orgId") Long orgId) - 실제로는 userId로 OrgMember를 검색하고 있음
문제점:
- 명명 불일치: API 클라이언트가 파라미터명을 보고
OrgMemberPK를 보낼 가능성 높음 → "member not found" 오류 발생 - 일관성 부재:
removeMemberFromOrg()도 동일한 패턴을 가지고 있음 - 유지보수성: 다른 개발자가 이 코드를 볼 때 의도를 파악하기 어려움
해결책:
- 파라미터명을
memberId→targetUserId로 변경하거나 - API 경로를
/members/{orgId}/{userId}로 변경 - API 문서에 명시적으로
memberId가 User ID임을 기록
removeMemberFromOrg() 메서드도 동일하게 수정하세요.
🤖 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 203 - 206, The parameter name memberId is misleading (it is
actually a User ID) — update the parameter to a clear name (e.g., targetUserId)
in OrgServiceImpl methods (including the method that contains the self-check
block and removeMemberFromOrg), update any PathVariable/endpoint mappings to
/members/{orgId}/{userId} or to use {targetUserId}, and change all internal
usages (calls to findByUserIdAndOrgId(memberId, orgId) →
findByUserIdAndOrgId(targetUserId, orgId) and related lookups) so variable names
and API path match the actual semantics; ensure exception messages and logging
(OrgHandler/OrgErrorCode usage) reference the new parameter name for clarity.
📌 관련 이슈
🚀 개요
📄 작업 내용
📸 스크린샷 / 테스트 결과 (선택)
✅ 체크리스트
🔍 리뷰 포인트 (Review Points)
Summary by CodeRabbit