Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
크리에이터 권한(인증) 심사 흐름을 분리/추가하기 위해 creator 서비스에 인증(심사) 도메인/영속/통신 기반을 깔고, user 서비스에는 닉네임 조회 gRPC 및 사용자 상태(정지/해제 등) 관련 변경을 확장하며, auth/user의 일부 유스케이스 시그니처를 Command/Query DTO 기반으로 정리한 PR입니다.
Changes:
- user 서비스:
GetUserNicknamegRPC 추가, 정지/정지해제 이벤트 및 상태 전이 로직 확장, Repository Port 메서드 정리(save/update/lock) - creator 서비스: 인증(심사) 도메인/영속/JPA/예외처리/커서 페이징 유틸 및 user 닉네임 gRPC 클라이언트 추가
- auth 서비스: 일부 유스케이스 입력을 Command DTO로 변경 및 DTO 패키지 정리
Reviewed changes
Copilot reviewed 51 out of 75 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| services/user/src/main/proto/user.proto | user gRPC에 닉네임 조회 RPC 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserSessionNotActiveException.java | 세션 비활성 예외 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserNotBannedException.java | 정지해제 불가 예외 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserBannedException.java | 정지 사용자 제한 예외 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/exception/UserAlreadyInactiveException.java | (삭제) 기존 비활성 예외 제거 |
| services/user/src/main/java/kr/magicbox/user/domain/event/UserUnbannedEvent.java | 유저 정지해제 도메인 이벤트 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/event/UserDomainEventType.java | USER_UNBANNED 타입 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/enums/UserStatus.java | INACTIVE 제거, BANNED 추가 |
| services/user/src/main/java/kr/magicbox/user/domain/aggregate/User.java | activate/ban/unban/delete 상태전이 정리 |
| services/user/src/main/java/kr/magicbox/user/application/service/WithdrawUserService.java | withdraw 커맨드화 + 락 조회 적용 |
| services/user/src/main/java/kr/magicbox/user/application/service/UserQueryService.java | 프로필 조회 Query DTO/Result 패키지 정리 |
| services/user/src/main/java/kr/magicbox/user/application/service/UserCommandService.java | 업데이트 커맨드 시그니처 정리 |
| services/user/src/main/java/kr/magicbox/user/application/service/UnbanUserService.java | 유저 정지해제 유스케이스 추가 |
| services/user/src/main/java/kr/magicbox/user/application/service/ManageUserSessionService.java | 세션 관리 Command DTO로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/service/LoginService.java | 저장/업데이트 메서드명 변경 반영 |
| services/user/src/main/java/kr/magicbox/user/application/service/GetUserNicknameService.java | 닉네임 조회 유스케이스 구현 추가 |
| services/user/src/main/java/kr/magicbox/user/application/service/CheckUserActiveService.java | active 체크 Query DTO 도입 |
| services/user/src/main/java/kr/magicbox/user/application/service/BanUserService.java | ban 도메인 메서드로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/out/UserRepositoryPort.java | save/update 및 withLock 조회 추가 |
| services/user/src/main/java/kr/magicbox/user/application/port/out/ReviewQueryPort.java | UserReviewResult 패키지 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/WithdrawUserUseCase.java | WithdrawUserCommand로 시그니처 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UserQueryUseCase.java | GetUserProfileQuery로 시그니처 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UserCommandUseCase.java | UpdateUserProfileCommand로 시그니처 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/UnbanUserUseCase.java | unban 유스케이스 포트 추가 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/ManageUserSessionUseCase.java | Start/EndSessionCommand로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/LoadUserCredentialUseCase.java | command/result 패키지 정리 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/GetUserNicknameUseCase.java | 닉네임 조회 포트 추가 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/CheckUserActiveUseCase.java | CheckUserActiveQuery로 변경 |
| services/user/src/main/java/kr/magicbox/user/application/port/in/BanUserUseCase.java | 포맷 정리(실질 변경 없음) |
| services/user/src/main/java/kr/magicbox/user/application/dto/UpdateUserProfileCommand.java | (삭제) 기존 커맨드 위치 제거 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/UserReviewResult.java | result 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/LoadUserCredentialResult.java | result 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/result/GetUserProfileResult.java | result 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/query/GetUserProfileQuery.java | 프로필 조회 Query DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/query/CheckUserActiveQuery.java | active 체크 Query DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/WithdrawUserCommand.java | withdraw Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/UpdateUserProfileCommand.java | update Command DTO로 재정의 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/UnbanUserCommand.java | unban Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/StartSessionCommand.java | start session Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/LoadUserCredentialCommand.java | command 패키지로 이동 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/EndSessionCommand.java | end session Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/application/dto/command/BanUserCommand.java | ban Command DTO 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/UserJpaAdapter.java | withLock 조회/메서드명(save/update) 반영 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/repository/UserJpaRepository.java | PESSIMISTIC_WRITE 조회 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/entity/UserEntity.java | status 업데이트 반영(+버전 제거) |
| services/user/src/main/java/kr/magicbox/user/adapter/out/communication/grpc/ReviewQueryGrpcAdapter.java | UserReviewResult 패키지 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserQueryController.java | Query DTO 기반 호출로 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserCommandController.java | Command DTO 기반 호출로 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/exception/handler/GlobalExceptionHandler.java | optimistic lock handler 제거 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/response/GetUserProfileResponse.java | response 패키지로 이동/의존 변경 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/request/UpdateUserProfileRequest.java | request 패키지로 이동 + userId 주입 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/web/AdminUserCommandController.java | unban 엔드포인트 추가 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/kafka/AuthEventKafkaListener.java | session 유스케이스 Command DTO 반영 |
| services/user/src/main/java/kr/magicbox/user/adapter/in/grpc/UserGrpcService.java | CheckUserActive Query DTO + GetUserNickname gRPC 구현 |
| services/creator/src/main/resources/application-prod.yml | creator-prod 환경 설정 추가 |
| services/creator/src/main/resources/application-local.yml | creator-local 설정 확장(grpc/kafka/jpa 등) |
| services/creator/src/main/resources/application-dev.yml | creator-dev 환경 설정 추가 |
| services/creator/src/main/proto/user.proto | creator 측 user gRPC stub용 proto 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/exception/SystemError.java | creator 예외 베이스(서버에러) 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/exception/BusinessException.java | creator 예외 베이스(비즈니스) 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/exception/BaseException.java | creator 공통 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/global/configuration/PropertiesConfiguration.java | @ConfigurationPropertiesScan 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/UserId.java | creator UserId VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/Nickname.java | creator Nickname VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorId.java | creator CreatorId VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorCertificationResult.java | 심사 결과 VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorCertificationRequest.java | 심사 요청 VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/vo/CreatorCertificationId.java | 인증 ID VO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/InvalidFieldException.java | 입력 검증 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/InvalidCertificationReviewStatusException.java | PENDING 리뷰 금지 예외 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/CreatorNotBannedException.java | 정지해제 불가 예외 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/CreatorAlreadyExistsException.java | 중복 creator 예외 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/CertificationNotFoundException.java | 인증 신청 미존재 예외 |
| services/creator/src/main/java/kr/magicbox/creator/domain/exception/CertificationAlreadyReviewedException.java | 중복 심사 예외 |
| services/creator/src/main/java/kr/magicbox/creator/domain/event/CreatorDomainEventType.java | creator 이벤트 타입 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/event/CreatorDomainEvent.java | creator 이벤트 인터페이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/event/CertificationRejectedEvent.java | 인증 거절 이벤트 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/event/CertificationApprovedEvent.java | 인증 승인 이벤트 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/enums/MagicGenre.java | 장르 enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/enums/CreatorStatus.java | creator 상태 enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/enums/CreatorCertificationStatus.java | 인증 상태 enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/constants/CreatorPolicyConstants.java | 정책 상수 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/aggregate/CreatorCertification.java | 인증 도메인 로직 추가 |
| services/creator/src/main/java/kr/magicbox/creator/domain/aggregate/Creator.java | creator 도메인 로직 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/service/certification/ReviewCertificationService.java | 심사 승인/거절 유스케이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/service/certification/GetAllPendingCertificationsService.java | pending 목록 조회 유스케이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/out/UserNicknameQueryPort.java | user 닉네임 조회 포트 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/out/CreatorRepositoryPort.java | creator 저장소 포트 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/out/CreatorDomainEventRepositoryPort.java | creator 이벤트 저장 포트 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/out/CreatorCertificationRepositoryPort.java | 인증 저장소 포트 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/in/ReviewCreatorCertificationUseCase.java | (중복) 심사 유스케이스 포트 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/in/ReviewCertificationUseCase.java | 심사 유스케이스 포트 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/port/in/GetAllPendingCreatorCertificationsUseCase.java | pending 조회 포트 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/result/PendingCertificationResult.java | pending 응답 result 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/query/GetAllPendingCertificationsQuery.java | pending 조회 query 추가 |
| services/creator/src/main/java/kr/magicbox/creator/application/dto/command/ReviewCertificationCommand.java | 심사 커맨드 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/vo/CreatorCertificationResultVO.java | 인증 결과 VO(임베디드) |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/vo/CreatorCertificationRequestVO.java | 인증 요청 VO(임베디드) |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/repository/CreatorJpaRepository.java | creator 조회/락/커서 쿼리 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/repository/CreatorDomainEventRepository.java | creator 이벤트 JPA repo 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/repository/CreatorCertificationJpaRepository.java | 인증 JPA repo + 커서 쿼리 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorMapper.java | creator mapper 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorCertificationRequestMapper.java | request VO mapper 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorCertificationMapper.java | 인증 mapper 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CertificationResultMapper.java | result VO mapper 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/entity/CreatorEntity.java | creator 엔티티 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/entity/CreatorDomainEventEntity.java | creator 이벤트 엔티티 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/entity/CreatorCertificationEntity.java | 인증 엔티티(+@Version) 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/entity/BaseEntity.java | 공통 엔티티 베이스 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/CreatorJpaAdapter.java | creator JPA 어댑터 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/CreatorDomainEventAdapter.java | creator 이벤트 저장 어댑터 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/CreatorCertificationJpaAdapter.java | 인증 JPA 어댑터 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/configuration/JpaConfiguration.java | JPA Auditing 활성화 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/communication/ServiceHost.java | grpc 채널 host enum 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/communication/grpc/UserNicknameQueryGrpcAdapter.java | user 닉네임 gRPC 클라이언트 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/out/communication/grpc/exception/UserServiceUnavailableException.java | user 서비스 장애 예외 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/validation/CursorSizeValidator.java | size 범위 validator 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/validation/CursorSize.java | size 검증 어노테이션 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/exception/handler/GlobalExceptionHandler.java | creator 글로벌 예외 처리 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/exception/handler/ErrorResponse.java | 에러 응답 모델 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/response/PendingCertificationResponse.java | pending 응답 DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/response/CursorResponse.java | 커서 응답 래퍼 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/request/ReviewCertificationRequest.java | 심사 요청 DTO 추가 |
| services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/constants/CursorConstants.java | 커서 상수 추가 |
| services/creator/build.gradle | creator 모듈 의존성/protobuf 설정 추가 |
| services/auth/src/main/java/kr/magicbox/auth/domain/exception/UserInactiveException.java | 비활성 사용자 예외 추가/명칭 변경 |
| services/auth/src/main/java/kr/magicbox/auth/domain/exception/UserBannedException.java | 정지 사용자 예외 추가 |
| services/auth/src/main/java/kr/magicbox/auth/domain/exception/InActiveUserException.java | (삭제) 기존 예외 제거 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/RefreshTokenService.java | RefreshTokenCommand 도입 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/LogoutService.java | LogoutCommand 도입 + 예외 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/LoginService.java | DTO 패키지 정리(import 변경) |
| services/auth/src/main/java/kr/magicbox/auth/application/service/HandleUserWithdrawnService.java | HandleUserWithdrawnCommand 도입 |
| services/auth/src/main/java/kr/magicbox/auth/application/service/HandleUserBannedService.java | HandleUserBannedCommand 도입 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/out/UserCredentialPort.java | UserResult 패키지 변경 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/RefreshTokenUseCase.java | RefreshTokenCommand 시그니처 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/LogoutUseCase.java | LogoutCommand 시그니처 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/LoginUseCase.java | DTO 패키지 정리 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserWithdrawnUseCase.java | HandleUserWithdrawnCommand 시그니처 |
| services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserBannedUseCase.java | HandleUserBannedCommand 시그니처 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/UserResult.java | result 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/TokenResult.java | result 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/result/IssueTokenResult.java | result 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/RefreshTokenCommand.java | refresh command 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LogoutCommand.java | logout command 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LoginCommand.java | command 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/HandleUserWithdrawnCommand.java | withdrawn command 추가 |
| services/auth/src/main/java/kr/magicbox/auth/application/dto/command/HandleUserBannedCommand.java | banned command 추가 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/out/communication/grpc/UserGrpcAdapter.java | UserResult 패키지 변경 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/dto/response/AccessTokenResponse.java | response 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/dto/request/LoginRequest.java | request 패키지로 이동 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/web/AuthCommandController.java | refresh/logout command 적용 + DTO 경로 변경 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/security/oauth2/OAuth2LoginSuccessHandler.java | UserResult 패키지 변경 |
| services/auth/src/main/java/kr/magicbox/auth/adapter/in/kafka/UserEventKafkaListener.java | 이벤트 핸들링 Command DTO 적용 |
| .github/workflows/sonarcloud-analyze.yml | Sonar 분석용 JDK/바이너리 설정 추가 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Service | ||
| @RequiredArgsConstructor | ||
| public class ReviewCertificationService implements ReviewCertificationUseCase { | ||
|
|
||
| private final CreatorCertificationRepositoryPort certificationRepositoryPort; | ||
| private final CreatorRepositoryPort creatorRepositoryPort; | ||
| private final CreatorDomainEventRepositoryPort eventRepositoryPort; | ||
| private final UserNicknameQueryPort userNicknameQueryPort; | ||
|
|
||
| @Transactional | ||
| @Override | ||
| public void reviewCertification(ReviewCertificationCommand command) { | ||
| CreatorCertification certification = certificationRepositoryPort.findById(command.certificationId()) | ||
| .orElseThrow(CertificationNotFoundException::new); | ||
|
|
||
| certification.review(command.certificationStatus(), CreatorCertificationResult.of(command.reviewMessage())); | ||
| certificationRepositoryPort.update(certification); | ||
|
|
||
| if (certification.isApproved()) { | ||
| createCreator(certification); | ||
| eventRepositoryPort.save(buildApprovedEvent(certification)); | ||
| } | ||
| else { | ||
| eventRepositoryPort.save(buildRejectedEvent(certification)); | ||
| } | ||
| } |
There was a problem hiding this comment.
PR 설명에는 'pending 목록 조회 API' 및 '승인/거절 처리 API' 구현이 포함되어 있는데, 현재 creator 서비스 코드에는 이를 노출하는 Web Controller(@RestController/@RequestMapping)가 추가되지 않았습니다(검색 기준 controller 클래스 부재). 실제 API 엔드포인트를 추가하거나, 본 PR 범위를 '도메인/애플리케이션 레이어 구현'으로 수정해 주세요.
| package kr.magicbox.creator.application.port.in; | ||
|
|
||
| import kr.magicbox.creator.application.dto.command.ReviewCertificationCommand; | ||
|
|
||
| public interface ReviewCreatorCertificationUseCase { | ||
|
|
||
| void reviewCreatorCertification(ReviewCertificationCommand command); | ||
| } |
There was a problem hiding this comment.
ReviewCreatorCertificationUseCase와 ReviewCertificationUseCase가 동일한 책임(심사 승인/거절)을 가지는 것으로 보이는데, 현재 구현체는 ReviewCertificationUseCase만 구현하고 다른 인터페이스는 사용처가 없습니다. 중복/미사용 인터페이스는 삭제하거나 하나로 통일해 API 경계를 명확히 해 주세요.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f-lab-ted
left a comment
There was a problem hiding this comment.
리뷰 요약
파악 내용
이 PR은 크리에이터 권한 요청에 대한 관리자 심사(승인/거절) API를 구현한 것으로, 헥사고날 아키텍처를 기반으로 Admin 전용 Command/Query 컨트롤러, 심사 UseCase/Service, 커서 기반 페이징, 도메인 이벤트 발행 등을 포함하고 있습니다. 도메인 레이어에서 상태 전이 검증(review() 메서드)과 비즈니스 예외 처리가 잘 분리되어 있으며, 승인 시 Creator 생성 + 이벤트 발행, 거절 시 이벤트 발행의 흐름이 명확하게 구현되어 있습니다. 커서 페이징, 커스텀 Validation 어노테이션(@CursorSize), CursorResponse 제네릭 유틸 등 재사용 가능한 컴포넌트도 잘 설계되어 있습니다.
잘한 점
도메인 어그리거트(CreatorCertification)에 상태 전이 로직과 검증을 집중시켜 비즈니스 규칙이 명확히 드러나며, 이벤트 기반 비동기 처리를 위한 도메인 이벤트 저장 구조가 잘 갖춰져 있습니다.
보완할 점
- 관리자 인증/인가 부재: Admin 컨트롤러에
@PreAuthorize,@Secured등 관리자 권한 검증이 전혀 없어 일반 사용자도 심사 API에 접근 가능한 보안 취약점이 존재합니다. - 커서 페이징 중복 조회 버그:
findAllByStatusWithCursor쿼리에서c.id >= :cursorId조건으로 인해 이전 페이지의 마지막 항목이 다음 페이지에 중복 포함됩니다. - 심사 요청 시 PENDING 상태 허용: API 레벨에서
CreatorCertificationStatus전체를 받아들이므로, 클라이언트가 PENDING을 보내면 도메인 예외가 발생합니다. API 경계에서 입력 가능한 상태를 제한해야 합니다. - 심사자 정보 미기록: 누가 승인/거절했는지 추적할 수 없어 감사(audit) 관점에서 문제가 됩니다.
결론
관리자 인증/인가 부재와 커서 페이징 버그는 반드시 수정 바랍니다. Request Changes 드립니다.
| @RestController | ||
| @RequestMapping("/api/creator/certification") | ||
| @RequiredArgsConstructor | ||
| public class AdminCreatorCertificationCommandController { |
There was a problem hiding this comment.
[Critical] 관리자 인증/인가 검증 부재
이 컨트롤러는 관리자만 접근해야 하는 심사 API인데, @PreAuthorize("hasRole('ADMIN')") 또는 @Secured("ROLE_ADMIN") 같은 인가 처리가 전혀 없습니다. 일반 사용자도 이 엔드포인트를 호출하여 인증 요청을 승인/거절할 수 있는 보안 취약점입니다.
또한 @AuthenticationPrincipal도 없어서 누가 심사했는지 기록할 수 없습니다. 감사(audit) 추적을 위해 심사자 ID를 command에 포함시키는 것을 권장합니다.
@PreAuthorize("hasRole('ADMIN')")
@PatchMapping("/{creatorCertificationId}/review")
public ResponseEntity<Void> reviewCertification(
@AuthenticationPrincipal AdminId adminId,
@PathVariable Long creatorCertificationId,
@Valid @RequestBody ReviewCertificationRequest request
) {
reviewCreatorCertificationUseCase.reviewCreatorCertification(
request.toCommand(creatorCertificationId, adminId));
...
}There was a problem hiding this comment.
관리자 인증은 Istio 딴에서 처리하려고 하고 있습니다
| @Query("SELECT c FROM CreatorCertificationEntity c WHERE c.userId = :userId AND (:cursorId IS NULL OR c.id <= :cursorId) ORDER BY c.id DESC LIMIT :size") | ||
| List<CreatorCertificationEntity> findAllByUserIdWithCursor(@Param("userId") Long userId, @Param("cursorId") Long cursorId, @Param("size") int size); | ||
|
|
||
| @Query("SELECT c FROM CreatorCertificationEntity c WHERE c.status = :status AND (:cursorId IS NULL OR c.id >= :cursorId) ORDER BY c.id ASC LIMIT :size") |
There was a problem hiding this comment.
[Critical] 커서 페이징 중복 조회 버그
c.id >= :cursorId 조건으로 인해 이전 페이지의 마지막 항목이 다음 페이지에도 포함됩니다.
컨트롤러에서 CursorResponse의 nextCursor는 현재 페이지 마지막 항목의 ID로 설정됩니다. 다음 요청 시 해당 ID가 cursor로 들어오면 id >= cursor이므로 해당 항목이 다시 조회됩니다.
>=를 >로 변경해야 합니다:
-- Before
WHERE c.status = :status AND (:cursorId IS NULL OR c.id >= :cursorId)
-- After
WHERE c.status = :status AND (:cursorId IS NULL OR c.id > :cursorId)참고로 같은 파일의 findAllByUserIdWithCursor도 동일한 이슈가 있습니다 (<= → <).
|
|
||
| @Builder | ||
| public record ReviewCertificationRequest( | ||
| @NotNull(message = "인증 상태는 필수입니다.") CreatorCertificationStatus status, |
There was a problem hiding this comment.
[Major] API 경계에서 심사 가능 상태를 제한해야 합니다
CreatorCertificationStatus에는 PENDING, APPROVED, REJECTED 세 값이 있는데, 심사 요청에서 PENDING을 보내면 도메인 레이어(CreatorCertification.review())에서 InvalidCertificationReviewStatusException이 발생합니다.
도메인 예외에 의존하기보다, API 경계에서 입력 가능한 값을 제한하는 것이 바람직합니다. 방법 예시:
- 심사 전용 enum을 별도로 정의 (예:
ReviewDecision { APPROVED, REJECTED })하고 request에서 사용 - 또는 커스텀 validation으로
PENDING값을 거부
이렇게 하면 잘못된 입력에 대해 400 Bad Request를 명확한 메시지와 함께 응답할 수 있고, 도메인 로직 진입 전에 빠르게 실패합니다.
| @RestController | ||
| @RequestMapping("/api/creator/certification") | ||
| @RequiredArgsConstructor | ||
| @Validated |
There was a problem hiding this comment.
[Major] 관리자 인가 검증 부재
Command 컨트롤러와 동일하게, pending 목록 조회도 관리자 전용 기능이지만 인가 처리가 없습니다. @PreAuthorize("hasRole('ADMIN')") 등을 추가하거나, URL 경로를 /api/admin/creator/certification으로 분리하여 Spring Security filter chain에서 일괄 처리하는 것을 권장합니다.
현재 /api/creator/certification 경로를 사용자 컨트롤러(CreatorCertificationCommandController)와 공유하고 있어, 경로만으로는 관리자 API를 구분할 수 없습니다.
There was a problem hiding this comment.
관리자 인증은 Istio 딴에서 처리하려고 하고 있습니다
| @@ -0,0 +1,77 @@ | |||
| package kr.magicbox.creator.application.service.certification; | |||
There was a problem hiding this comment.
[Major] 심사자(Reviewer) 정보 미기록
현재 ReviewCertificationCommand에 심사자 ID가 포함되어 있지 않아, 누가 해당 인증을 승인/거절했는지 추적할 수 없습니다. 운영 관점에서 감사(audit) 추적이 불가능한 것은 문제가 될 수 있습니다.
ReviewCertificationCommand에 reviewerId (또는 adminId)를 추가하고, CreatorCertificationResult에도 심사자 정보를 포함시키는 것을 권장합니다.
There was a problem hiding this comment.
넵 그것이 맞다고 생각하여서 포함시켰습니다!
| @@ -0,0 +1,77 @@ | |||
| package kr.magicbox.creator.application.service.certification; | |||
There was a problem hiding this comment.
[Minor] 이벤트의 reviewedAt과 도메인의 reviewedAt 불일치 가능성
CreatorCertificationResult.of(command.reviewMessage())에서 Instant.now()로 reviewedAt이 설정되고, 이후 buildApprovedEvent/buildRejectedEvent에서 다시 Instant.now()를 호출합니다. 두 시점 사이에 미세한 시간 차이가 발생할 수 있습니다.
이벤트의 reviewedAt은 도메인에 기록된 값을 사용하는 것이 정합성 측면에서 바람직합니다:
private CertificationApprovedEvent buildApprovedEvent(CreatorCertification certification) {
return CertificationApprovedEvent.builder()
.userId(certification.getUserId())
.certificationId(certification.getId())
.reviewedAt(certification.getResult().reviewedAt()) // 도메인 값 사용
.build();
}There was a problem hiding this comment.
넵 그렇게 사용하는 것이 더 정확할 것 같습니다!
| @@ -0,0 +1,77 @@ | |||
| package kr.magicbox.creator.application.service.certification; | |||
There was a problem hiding this comment.
[Major] 승인 시 Creator 중복 생성 방지 로직 부재
createCreator()에서 creatorRepositoryPort.save(creator)를 호출하는데, 해당 userId로 이미 Creator가 존재하는 경우에 대한 검증이 없습니다.
예를 들어, 동일 사용자가 과거에 승인되어 Creator가 이미 존재하는 상태에서 새로운 인증 요청이 승인되면 중복 Creator가 생성될 수 있습니다. DB 유니크 제약조건(CreatorEntity의 userId unique)에 의해 예외가 발생하겠지만, 비즈니스 로직에서 명시적으로 검증하는 것이 바람직합니다.
private void createCreator(CreatorCertification certification) {
creatorRepositoryPort.findByUserId(certification.getUserId())
.ifPresent(c -> { throw new CreatorAlreadyExistsException(); });
...
}There was a problem hiding this comment.
existsByUserId로 검증했습니다!
|
|
||
| @RestController | ||
| @RequestMapping("/api/creator/certification") | ||
| @RequiredArgsConstructor |
There was a problem hiding this comment.
[Suggestion] Admin API 경로 분리 제안
현재 Admin 컨트롤러와 사용자 컨트롤러가 동일한 base path(/api/creator/certification)를 사용합니다. Admin API를 /api/admin/creator/certification으로 분리하면:
- Spring Security에서
/api/admin/**패턴으로 일괄 인가 처리 가능 - API Gateway 레벨에서 admin 요청 라우팅/로깅 분리 가능
- API 문서에서 관리자/사용자 API 구분이 명확해짐
마이크로서비스 환경에서 관리자 트래픽과 사용자 트래픽을 분리하는 것은 운영 측면에서도 유리합니다.
There was a problem hiding this comment.
이것은 지금 현재 pr들이 머지되면 한 번에 반영하겠습니다
|



📌 관련 이슈
📝 변경 사항 요약
작업 유형
(기능 단위 완료 후 PR)(버그 1개 = PR 1개)(작업 단위 완료 후 PR)(작업 단위 완료 후 PR)변경 내용
변경 이유
✅ 테스트 체크리스트
📸 스크린샷 / 로그
펼쳐보기
🔄 동작 플로우 (Mermaid)
%%{init: {'flowchart': {'useMaxWidth': true, 'htmlLabels': true}} }%% flowchart TD A[pending 조회] --> B[대상 선택] B --> C{승인/거절} C -->|승인| D[APPROVED 업데이트] C -->|거절| E[REJECTED 업데이트] D --> F[승인 이벤트 발행] E --> G[거절 이벤트 발행]💬 리뷰어에게
크리에이터 권한 요청 승인/거절 API 구현 범위를 중심으로 리뷰 부탁드립니다.