Skip to content

feat/45 :: 크리에이터 권한 요청 API 구현#12

Open
lian2945 wants to merge 17 commits intofeat/98from
feat/45
Open

feat/45 :: 크리에이터 권한 요청 API 구현#12
lian2945 wants to merge 17 commits intofeat/98from
feat/45

Conversation

@lian2945
Copy link
Copy Markdown
Collaborator

@lian2945 lian2945 commented Apr 6, 2026

📌 관련 이슈

  • close #45

📝 변경 사항 요약

작업 유형

  • ✨ feat: 새로운 기능 추가 (기능 단위 완료 후 PR)
  • 🐛 fix: 버그 수정 (버그 1개 = PR 1개)
  • ♻️ refactor: 코드 리팩토링 (작업 단위 완료 후 PR)
  • ✅ test: 테스트 코드 추가/수정 (작업 단위 완료 후 PR)

변경 내용

  • 크리에이터 권한 요청 생성 API를 추가했습니다.
  • 요청 취소 및 내역 조회 흐름을 구현했습니다.
  • 요청 상태/결과 모델을 반영했습니다.

변경 이유

  • 크리에이터 전환 프로세스 시작점(요청) 기능을 독립 배포/리뷰 단위로 분리하기 위해서입니다.

✅ 테스트 체크리스트

  • 단위 테스트 작성 및 통과
  • 통합 테스트 통과
  • 기존 기능 정상 동작 확인 (Regression)
  • API 응답값 확인
  • 예외 케이스 처리 확인
  • 로컬 환경에서 직접 테스트 완료

📸 스크린샷 / 로그

펼쳐보기

🔄 동작 플로우 (Mermaid)

%%{init: {'flowchart': {'useMaxWidth': true, 'htmlLabels': true}} }%%
flowchart TD
    A[사용자<br/>권한 요청] --> B[요청 검증]
    B --> C[PENDING 저장]
    C --> D[요청 내역 조회]
    D --> E[심사 단계 전달]
Loading

💬 리뷰어에게

크리에이터 권한 요청 API 구현 범위를 중심으로 리뷰 부탁드립니다.

Copilot AI review requested due to automatic review settings April 6, 2026 10:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

크리에이터 전환(권한 요청) 프로세스의 시작점인 “크리에이터 인증 신청/취소/내역 조회” 기능을 creator 서비스에 추가하고, 연관된 user/auth 서비스의 상태/세션 처리 및 DTO 구조를 정리한 PR입니다.

Changes:

  • creator 서비스에 크리에이터 인증 신청/취소/내역 조회 API 및 JPA 기반 저장소/도메인 모델 추가
  • user/auth 서비스에 command/query/result DTO 분리 적용 및 사용자 상태(밴/언밴, 세션) 관련 로직/포트 시그니처 정리
  • SonarCloud 분석 워크플로우에서 Java 바이너리 경로 설정 및 사전 컴파일 단계 추가

Reviewed changes

Copilot reviewed 109 out of 130 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
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 UserStatus에 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 WithdrawUserCommand 및 락 조회/포트 메서드명 변경 반영
services/user/src/main/java/kr/magicbox/user/application/service/UserQueryService.java GetUserProfileQuery 기반 조회로 시그니처 변경
services/user/src/main/java/kr/magicbox/user/application/service/UserCommandService.java UpdateUserProfileCommand 단일 인자 방식으로 변경
services/user/src/main/java/kr/magicbox/user/application/service/UnbanUserService.java 언밴 유스케이스/서비스 추가
services/user/src/main/java/kr/magicbox/user/application/service/ManageUserSessionService.java Start/EndSessionCommand 도입 및 예외 변경
services/user/src/main/java/kr/magicbox/user/application/service/LoginService.java command/result 패키지 분리 및 포트 메서드명 변경 반영
services/user/src/main/java/kr/magicbox/user/application/service/CheckUserActiveService.java CheckUserActiveQuery 기반 조회로 변경
services/user/src/main/java/kr/magicbox/user/application/service/BanUserService.java ban() 도메인 메서드 및 update() 포트 사용
services/user/src/main/java/kr/magicbox/user/application/port/out/UserRepositoryPort.java withLock 조회 및 save/update 메서드명 변경
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 언밴 유스케이스 추가
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/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 기존 dto 위치의 커맨드 제거
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 활성 여부 조회 Query DTO 추가
services/user/src/main/java/kr/magicbox/user/application/dto/command/WithdrawUserCommand.java 탈퇴 Command DTO 추가
services/user/src/main/java/kr/magicbox/user/application/dto/command/UpdateUserProfileCommand.java 프로필 수정 Command DTO 추가(userId 포함)
services/user/src/main/java/kr/magicbox/user/application/dto/command/UnbanUserCommand.java 언밴 Command DTO 추가(현재 사용처 제한적)
services/user/src/main/java/kr/magicbox/user/application/dto/command/StartSessionCommand.java 세션 시작 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 세션 종료 Command DTO 추가
services/user/src/main/java/kr/magicbox/user/application/dto/command/BanUserCommand.java 밴 Command DTO 추가
services/user/src/main/java/kr/magicbox/user/adapter/out/persistence/UserJpaAdapter.java save/update 및 withLock 조회 구현 추가
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 @Version 제거 및 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 GetUserProfileQuery 생성 방식으로 변경
services/user/src/main/java/kr/magicbox/user/adapter/in/web/UserCommandController.java update/withdraw에 Command DTO 적용
services/user/src/main/java/kr/magicbox/user/adapter/in/web/exception/handler/GlobalExceptionHandler.java Optimistic lock 예외 핸들러 제거
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 언밴 엔드포인트 추가
services/user/src/main/java/kr/magicbox/user/adapter/in/kafka/AuthEventKafkaListener.java 세션 관리 유스케이스 호출을 Command DTO로 변경
services/user/src/main/java/kr/magicbox/user/adapter/in/grpc/UserGrpcService.java checkUserActive 호출을 Query DTO로 변경
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 인터페이스 proto 추가
services/creator/src/main/java/kr/magicbox/creator/global/exception/SystemError.java 5xx 전용 시스템 예외 추가
services/creator/src/main/java/kr/magicbox/creator/global/exception/BusinessException.java 4xx 전용 비즈니스 예외 추가
services/creator/src/main/java/kr/magicbox/creator/global/exception/BaseException.java 공통 BaseException 추가
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/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/CertificationPendingAlreadyExistsException.java PENDING 중복 신청 예외 추가
services/creator/src/main/java/kr/magicbox/creator/domain/enums/MagicGenre.java 장르 enum 추가
services/creator/src/main/java/kr/magicbox/creator/domain/enums/CreatorCertificationStatus.java 인증 상태 enum 추가
services/creator/src/main/java/kr/magicbox/creator/domain/aggregate/CreatorCertification.java 인증 애그리거트 및 생성/취소 검증 로직 추가
services/creator/src/main/java/kr/magicbox/creator/application/service/certification/GetMyCertificationsService.java 내 인증 내역 조회 서비스 추가
services/creator/src/main/java/kr/magicbox/creator/application/service/certification/CancelCreatorCertificationService.java 인증 신청 취소 서비스 추가
services/creator/src/main/java/kr/magicbox/creator/application/service/certification/ApplyCreatorCertificationService.java 인증 신청 생성 서비스 추가
services/creator/src/main/java/kr/magicbox/creator/application/port/out/CreatorCertificationRepositoryPort.java 인증 저장소 포트 추가
services/creator/src/main/java/kr/magicbox/creator/application/port/in/GetMyCreatorCertificationsUseCase.java 내 인증 내역 유스케이스 추가
services/creator/src/main/java/kr/magicbox/creator/application/port/in/CancelCreatorCertificationUseCase.java 인증 취소 유스케이스 추가
services/creator/src/main/java/kr/magicbox/creator/application/port/in/ApplyCreatorCertificationUseCase.java 인증 신청 유스케이스 추가
services/creator/src/main/java/kr/magicbox/creator/application/dto/result/CreatorCertificationInfoResult.java 인증 조회 결과 DTO 추가
services/creator/src/main/java/kr/magicbox/creator/application/dto/query/GetMyCertificationsQuery.java 내 인증 조회 Query DTO 추가
services/creator/src/main/java/kr/magicbox/creator/application/dto/command/CancelCreatorCertificationCommand.java 인증 취소 Command DTO 추가
services/creator/src/main/java/kr/magicbox/creator/application/dto/command/ApplyCertificationCommand.java 인증 신청 Command DTO 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/vo/CreatorCertificationResultVO.java 결과 Embeddable VO 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/vo/CreatorCertificationRequestVO.java 요청 Embeddable VO 추가(장르 element collection 포함)
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/repository/CreatorCertificationJpaRepository.java 인증 JPA 리포지토리 및 커서 쿼리 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorCertificationRequestMapper.java 요청 VO 매퍼 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CreatorCertificationMapper.java 인증 엔티티/도메인 매퍼 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/mapper/CertificationResultMapper.java 결과 매퍼 추가
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 sonyflake id + auditing 베이스 엔티티 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/out/persistence/CreatorCertificationJpaAdapter.java 포트 구현 어댑터 추가
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 외부 서비스 호스트 enum 추가
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 에러 응답 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/response/CreatorCertificationInfoResponse.java 인증 조회 응답 DTO 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/dto/request/ApplyCertificationRequest.java 인증 신청 요청 DTO 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/CreatorCertificationQueryController.java 내 인증 내역 조회 API 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/CreatorCertificationCommandController.java 인증 신청/취소 API 추가
services/creator/src/main/java/kr/magicbox/creator/adapter/in/web/constants/CursorConstants.java 커서 기본값/범위 상수 추가
services/creator/build.gradle protobuf + spring grpc/클라우드 의존성 및 BOM 설정 추가
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 적용 및 result 패키지 반영
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 command/result 패키지 분리 반영
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 refresh 시그니처를 Command로 변경
services/auth/src/main/java/kr/magicbox/auth/application/port/in/LogoutUseCase.java logout 시그니처를 Command로 변경
services/auth/src/main/java/kr/magicbox/auth/application/port/in/LoginUseCase.java command/result 패키지 분리 반영
services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserWithdrawnUseCase.java Command 기반 시그니처로 변경
services/auth/src/main/java/kr/magicbox/auth/application/port/in/HandleUserBannedUseCase.java Command 기반 시그니처로 변경
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 DTO 추가
services/auth/src/main/java/kr/magicbox/auth/application/dto/command/LogoutCommand.java logout Command DTO 추가
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 유저 탈퇴 이벤트 처리 Command DTO 추가
services/auth/src/main/java/kr/magicbox/auth/application/dto/command/HandleUserBannedCommand.java 유저 밴 이벤트 처리 Command DTO 추가
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 SonarCloud 분석 전 Java 컴파일 및 binaries 설정 추가
Comments suppressed due to low confidence (1)

services/user/src/main/java/kr/magicbox/user/adapter/in/web/dto/request/UpdateUserProfileRequest.java:25

  • nickname 필드는 OptionalNotBlank로 null 허용인데, toCommand()에서 항상 Nickname.of(nickname)을 호출하고 있어 nickname 미전달 PATCH 요청 시 NPE/검증 예외가 발생합니다. nickname이 null이면 Nickname 변환을 건너뛰도록 처리해야 합니다.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +27
CreatorCertification certification = certificationRepositoryPort.findById(command.creatorCertificationId())
.orElseThrow(CertificationNotFoundException::new);

certification.validateCancellable(command.userId());

certificationRepositoryPort.deleteById(command.creatorCertificationId());
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

필드로 주입된 것은 certificationRepositoryPort인데, 삭제 호출에서 certificationRepositoryPort.deleteById(...)를 사용하고 있어 컴파일이 되지 않습니다. 동일한 필드명으로 호출하도록 수정이 필요합니다.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +29
public void applyCreatorCertification(ApplyCertificationCommand command) {
log.info("서비스 통과");
List<CreatorCertification> existingCertifications =
certificationRepositoryPort.findAllByUserId(command.userId());
log.info("서비스 통과1, {}", existingCertifications.size());

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

현재 log.info("서비스 통과") / 서비스 통과1 는 디버그성 로그로 보이며, 프로덕션 로그 노이즈 및 비용을 유발합니다. 의미 있는 메시지로 변경하거나 debug 레벨로 낮추거나 제거하는 것이 좋습니다.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

넵 알겠습니다!

Comment on lines 3 to 6
public enum UserStatus {
ACTIVE,
INACTIVE,
BANNED,
DELETED
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

UserStatus enum에서 INACTIVE가 제거되고 BANNED가 추가되었습니다. 이 enum이 DB에 문자열로 저장되고 있다면(일반적인 JPA EnumType.STRING) 기존 INACTIVE 데이터가 역직렬화되지 않아 장애로 이어질 수 있어, 배포 전 데이터 마이그레이션/호환 전략이 필요합니다.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Flyway를 도입하겠습니다. 추후

Comment on lines +20 to +49
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'

- name: Compile Java classes
run: ./services/auth/gradlew --no-daemon classes

- name: Set SonarCloud Project Key
run: |
REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2)
ORG_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 1)
SONAR_PROJECT_KEY="${ORG_NAME}_${REPO_NAME}"
echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV
SONAR_JAVA_BINARIES=$(find . -type d -path "*/build/classes/java/main" | sed 's|^\./||' | paste -sd "," -)
echo "SONAR_PROJECT_KEY=$SONAR_PROJECT_KEY" >> $GITHUB_ENV
echo "SONAR_JAVA_BINARIES=$SONAR_JAVA_BINARIES" >> $GITHUB_ENV

- name: Analyze with SonarCloud
uses: SonarSource/sonarcloud-github-action@master
id: analyze-sonarcloud
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.SECRET_GITHUB_BOT }}
SONAR_TOKEN: ${{ secrets.SECRET_SONARQUBE }}
with:
args:
-Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
-Dsonar.organization=f-lab-edu-1
-Dsonar.java.binaries=${{ env.SONAR_JAVA_BINARIES }}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

PR 설명은 '크리에이터 권한 요청 API' 중심인데, 이 워크플로우 변경(자바 컴파일 단계 추가 및 sonar.java.binaries 설정)처럼 CI/분석 파이프라인 변경도 포함되어 있습니다. 리뷰/배포 범위가 커지므로 PR 설명에 해당 변경 범위를 명시하거나 PR을 분리하는 것이 좋습니다.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

그 전에 했었는데, 적용이 안 되어서 그렇습니다.

lian2945 and others added 5 commits April 6, 2026 20:09
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>
lian2945 and others added 5 commits April 6, 2026 20:16
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>
@lian2945 lian2945 requested a review from f-lab-ted April 8, 2026 02:54
Copy link
Copy Markdown

@f-lab-ted f-lab-ted left a comment

Choose a reason for hiding this comment

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

리뷰 요약

크리에이터 권한 인증 요청(신청/취소/내역 조회) API를 헥사고널 아키텍처 기반으로 구현한 PR입니다. Command/Query 분리(CQRS 패턴), 도메인 애그리거트(CreatorCertification)에 비즈니스 규칙 집중, 커서 기반 페이지네이션, 그리고 도메인 VO를 통한 유효성 검증 등의 구조를 중점적으로 리뷰했습니다.

헥사고널 아키텍처의 레이어 분리(adapter/application/domain)가 잘 지켜져 있고, 도메인 내에 비즈니스 불변식(validateNoPendingCertification, validateCancellable)을 응집시킨 점, VO에서 자기 유효성을 보장하는 설계가 좋습니다.

다만, 몇 가지 주요 보완 사항이 있습니다:

  1. [Critical] CreatorCertificationResultVOof() 메서드에서 result가 null일 경우 NPE가 발생할 수 있으며, CreatorCertificationEntity.updateFromDomain()에서 null result를 그대로 전달합니다.
  2. [Major] 취소 시 deleteById를 사용하는데, 감사 추적(audit trail)이 불가능해지며 soft delete 또는 상태 변경 방식을 고려해야 합니다.
  3. [Major] ApplyCreatorCertificationService에서 findAllByUserId로 전체 이력을 조회하는 것은 데이터가 쌓일수록 비효율적이며, PENDING 상태만 조회하는 쿼리로 개선이 필요합니다.
  4. [Major] @AuthenticationPrincipal UserId userId가 Spring Security에서 바로 UserId 도메인 객체로 resolve 되려면 커스텀 ArgumentResolver 또는 UserDetails 구현이 필요한데, 관련 설정이 보이지 않습니다.

Critical 이슈인 NPE 가능성에 대해서는 반드시 수정 바랍니다. Major 이슈들에 대해서도 검토 후 반영 여부 결정해주세요.

}

public void updateFromDomain(CreatorCertification creatorCertification) {
this.status = creatorCertification.getStatus();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Critical] updateFromDomain에서 creatorCertification.getResult()가 null일 수 있습니다 (PENDING 상태). 이 경우 CreatorCertificationResultVO.of(null)이 호출되어 NPE가 발생합니다.

public void updateFromDomain(CreatorCertification creatorCertification) {
    this.status = creatorCertification.getStatus();
    this.request = CreatorCertificationRequestVO.of(creatorCertification.getRequest());
    this.result = creatorCertification.getResult() != null
            ? CreatorCertificationResultVO.of(creatorCertification.getResult())
            : null;
}

null 체크를 추가하거나, Optional을 활용해주세요.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

넵 정말 심각하네요..ㅠㅠ 수정하였습니다!


certification.validateCancellable(command.userId());

creatorCertificationRepositoryPort.deleteById(command.creatorCertificationId());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Major] 취소 시 deleteById로 물리 삭제를 수행하면 인증 신청 이력이 완전히 사라져 감사 추적(audit trail)이 불가능합니다.

DDD 관점에서 인증 신청 취소는 상태 전이(PENDING → CANCELLED)로 모델링하는 것이 자연스럽습니다. CreatorCertificationStatusCANCELLED 상태를 추가하고, 도메인 메서드(cancel())를 통해 상태를 변경하는 방식을 권장합니다.

이렇게 하면:

  • 사용자의 과거 신청/취소 이력을 추적할 수 있고
  • validateNoPendingCertification 로직도 자연스럽게 동작하며
  • 향후 취소 사유 등 추가 요구사항에도 대응이 용이합니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

네 훨씬 좋은 방안인 것 같습니다.
이 방향대로 수정했습니다.

@Transactional
@Override
public void applyCreatorCertification(ApplyCertificationCommand command) {
List<CreatorCertification> existingCertifications =
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Major] findAllByUserId로 해당 유저의 전체 인증 이력을 메모리에 로드한 후, 스트림으로 PENDING 존재 여부를 확인합니다. 이력이 쌓일수록 불필요한 데이터 로딩이 발생합니다.

CreatorCertificationRepositoryPortexistsByUserIdAndStatus(UserId, CreatorCertificationStatus) 같은 메서드를 추가하여, DB 레벨에서 PENDING 상태 존재 여부만 확인하는 것이 효율적입니다.

// Port
boolean existsByUserIdAndStatus(UserId userId, CreatorCertificationStatus status);

// Service
if (certificationRepositoryPort.existsByUserIdAndStatus(command.userId(), PENDING)) {
    throw new CertificationPendingAlreadyExistsException();
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

넵 잘못된 방식으로 코드를 짜고 있었네요 수정하였습니다!


@PostMapping
public ResponseEntity<Void> apply(
@AuthenticationPrincipal UserId userId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Major] @AuthenticationPrincipal UserId userId로 도메인 VO를 직접 주입받고 있는데, Spring Security의 기본 AuthenticationPrincipalUserDetails 또는 Authentication.getPrincipal() 객체를 주입합니다.

UserId 도메인 객체를 직접 resolve하려면 커스텀 AuthenticationPrincipalArgumentResolver 또는 @AuthenticationPrincipal 어노테이션의 expression 속성 설정이 필요합니다. 현재 코드에서는 관련 설정이 보이지 않아, 런타임에 타입 캐스팅 오류가 발생할 수 있습니다.

Security 설정과 함께 UserId resolve 방식을 확인/추가해주세요.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

수정하였습니다!

@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = CursorConstants.DEFAULT_SIZE) @CursorSize Integer size) {
List<CreatorCertificationInfoResponse> content =
getMyCreatorCertificationsUseCase.getMyCertifications(GetMyCertificationsQuery.of(userId, cursor, size + 1))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Minor] size + 1을 서비스 레이어가 아닌 컨트롤러에서 계산하여 쿼리에 전달하고 있습니다. "다음 페이지 존재 여부 확인을 위해 1개 더 조회"하는 것은 페이지네이션 인프라 로직인데, 이 책임이 adapter 레이어에 위치해 있습니다.

이 로직이 컨트롤러에 노출되면:

  • 다른 adapter(gRPC, 이벤트 핸들러 등)에서 같은 UseCase를 호출할 때 동일한 +1 로직을 반복해야 합니다.
  • CursorResponse.of와 암묵적 계약이 생깁니다.

GetMyCertificationsService 내부에서 size + 1 조회 후, 페이지네이션 정보를 포함한 결과 DTO를 반환하는 방식을 고려해주세요.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

수정하였습니다!

@sonarqubecloud
Copy link
Copy Markdown

@lian2945 lian2945 requested a review from f-lab-ted April 14, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants