Skip to content

Conversation

@kanghana1
Copy link
Contributor

@kanghana1 kanghana1 commented Jan 5, 2026

✅ 체크리스트

  • merge 타겟 브랜치 잘 설정되었는지 확인하기 (fe/dev, be/dev)

🔥 연관 이슈

해결되는 Issue의 경우 closes나 resolves 키워드를 사용해주세요.

🚀 작업 내용

  1. organization에 소속되어있는 도메인 구현
  2. organization의 outbound port 설계
  3. organization의 inbound port는 web api 부분 설계

💬 리뷰 중점사항

코드 중에서 특별히 고려하였거나 토론하고 싶은 부분이 있다면 명시해주세요.

인터페이스를 용도에 맞게 분리하니까 개수가 너무 많은 거 같은데 괜찮을까요 ?

📃 관련 문서

Team Document Hub나 Wiki에 작성한 문서가 있다면 링크를 첨부해주세요.

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 학교 관리 API 추가(목록/상세조회, 등록, 수정, 삭제, 일괄 삭제)
    • 기수 관리 API 추가(목록/등록/수정/삭제/현재 기수 지정)
    • 지부 관리 API 추가(조회, 생성)
    • 페이지네이션 지원 추가(PageRequest/PageResult)
  • Documentation

    • 학교·기수·지부 관리 API 문서 추가
  • Tests

    • 컨트롤러 대상 REST Docs 테스트 추가
  • Refactor

    • JPA 감사 설정 분리 및 설정 정리, import 정리

✏️ Tip: You can customize this high-level summary in your review settings.

@kanghana1 kanghana1 self-assigned this Jan 5, 2026
@kanghana1 kanghana1 requested a review from kyeoungwoon as a code owner January 5, 2026 00:26
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

조직 도메인(학교/기수/지부/스터디그룹) 엔티티, 포트·유스케이스, 웹 컨트롤러, DTO, 검증 및 API 문서가 추가되고 JPA 감사 설정이 JpaConfig로 이동했습니다. 일부 import 정리 및 테스트·문서용 REST Docs 테스트가 추가되었습니다.

Changes

Cohort / File(s) 변경 사항
Config & Boot
src/main/java/com/umc/product/global/config/P6SpyConfig.java, src/main/java/com/umc/product/global/config/SecurityConfig.java, src/main/java/com/umc/product/global/config/WebMvcConfig.java, src/main/java/com/umc/product/global/config/JpaConfig.java, src/main/java/com/umc/product/UmcProductApplication.java, .gitignore
import 정리 및 중복 제거; JPA 감사(@EnableJpaAuditing)를 UmcProductApplication에서 제거하고 JpaConfig로 이동
Global Exception & Security
src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java, src/main/java/com/umc/product/global/exception/constant/Domain.java, src/main/java/com/umc/product/global/security/*.java, src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
import 정리; Domain enum에 MEMBER, CHALLENGER 추가; CurrentUser@AuthenticationPrincipal 메타어노테이션 추가
Common Paging DTOs
src/main/java/com/umc/product/common/dto/request/PageRequest.java, src/main/java/com/umc/product/common/dto/request/PageResult.java
페이징 레코드 추가: PageRequest(page, limit) 및 offset(), PageResult<T>(items,totalCount,page,limit) 및 totalPages()
Domain Entities
src/main/java/com/umc/product/organization/domain/* ( School.java, Gisu.java, Chapter.java, CentralOrganization.java, ChapterSchool.java, StudyGroup.java, StudyGroupMember.java )
JPA 엔티티 신규 추가 및 생성시 검증 로직 포함(도메인 에러코드 사용), StudyGroup 멤버 관리 메서드 추가
Organization Error Codes
src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
조직 도메인 전용 에러 코드(enum) 다수 추가
Ports (Query & Command)
src/main/java/com/umc/product/organization/application/port/out/query/*, src/main/java/com/umc/product/organization/application/port/out/command/*
데이터 조회·영속성 관리용 포트 인터페이스 다수 추가 (findById, findAll, save, delete 등)
Use Cases (In: Query & Command)
src/main/java/com/umc/product/organization/application/port/in/query/*, src/main/java/com/umc/product/organization/application/port/in/command/*
조회/명령용 유스케이스 인터페이스 추가 (예: GetSchoolUseCase, ManageSchoolUseCase, ManageGisuUseCase 등)
Application DTOs (domain & command)
src/main/java/com/umc/product/organization/application/port/in/query/dto/*, src/main/java/com/umc/product/organization/application/port/in/command/dto/*
조회 결과 및 명령 전달용 record DTO 신규 추가 (SchoolInfo, GisuInfo, CreateSchoolCommand 등)
Web Controllers
src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java, SchoolQueryController.java, ChapterController.java, ChapterQueryController.java, GisuController.java, GisuQueryController.java
Admin REST 컨트롤러 추가(학교/지부/기수 CRUD 및 조회 엔드포인트), DTO 변환 및 유스케이스 위임
Web DTOs (request/response)
src/main/java/com/umc/product/organization/adapter/in/web/dto/request/*, src/main/java/com/umc/product/organization/adapter/in/web/dto/response/*
HTTP 요청/응답용 record DTO 및 toCommand()/from()/of() 변환 헬퍼 추가 (유효성 어노테이션 포함)
Service (stub)
src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
ManageSchoolUseCase 구현 스텁 추가(메서드 바디 비어있음)
Tests & Documentation tests
src/test/java/com/umc/product/support/DocumentationTest.java, 여러 *ControllerTest.java, src/test/java/com/umc/product/UmcProductApplicationTests.java(삭제)
DocumentationTest에 컨트롤러·유스케이스 MockitoBean 추가, REST Docs용 컨트롤러 테스트 다수 추가, 기존 ApplicationTests 삭제
AsciiDoc 문서
src/docs/asciidoc/index.adoc, src/docs/asciidoc/api/school/school.adoc, src/docs/asciidoc/api/chapter/chapter.adoc, src/docs/asciidoc/api/gisu/gisu.adoc, CLAUDE.md
학교/지부/기수 API 문서 추가 및 Manage 통합 옵션 안내 문서 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as SchoolController
    participant UseCase as ManageSchoolUseCase
    participant Port as ManageSchoolPort
    participant DB as Database

    Client->>Controller: POST /api/v1/admin/schools (CreateSchoolRequest)
    Controller->>UseCase: register(CreateSchoolCommand)
    UseCase->>Port: save(Central School Entity)
    Port->>DB: persist entity
    DB-->>Port: persisted entity with id
    Port-->>UseCase: return entity
    UseCase-->>Controller: (void / confirmation)
    Controller-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 안녕! 새로 온 학교와 기수, 지부, 스터디,

레코드로 다담아 명확히 포장했네,
포트와 유스케이스가 손을 맞잡고,
컨트롤러가 길을 내어 문서를 남기네,
당근 먹고 뛰어놀며 배포를 축하해! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항(Organization 도메인 port 설계 및 프로젝트 구조 확정)을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 대부분 작성되었으나 '작업 내용' 섹션이 불완전하고 체크리스트가 미체크 상태입니다.
Linked Issues check ✅ Passed PR 변경사항이 링크된 이슈 #5, #25의 목표(Organization 도메인 port 설계, aggregate 패턴 적용)를 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 Organization 도메인 port 설계 및 구조 확정과 관련되어 있으며 범위 내의 변경입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 21

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77f3310 and c48dd16.

📒 Files selected for processing (37)
  • .gitignore
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageResult.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
🧰 Additional context used
📓 Path-based instructions (8)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageResult.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageRequest.java
**/application/port/in/**UseCase.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/in/**UseCase.java: UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern
UseCase interfaces should return Info/DTO objects, not Entity objects directly

Files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageResult.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageRequest.java
**/application/port/out/**Port.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/out/**Port.java: Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle
Load Port methods must use Optional for single object returns and avoid returning Response DTOs
Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
**/*application/port/out/**Port.java

📄 CodeRabbit inference engine (CLAUDE.md)

Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
**/domain/**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

State changes in Entity classes must be performed through domain methods, not via setters

Files:

  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
**/*application/port/in/**/*{Command,Query}.java

📄 CodeRabbit inference engine (CLAUDE.md)

Command and Query records should be immutable with @NotNull validation and include conversion method toCommand() or toEntity()

Files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
🧠 Learnings (19)
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/out/persistence/**PersistenceAdapter.java : Persistence adapters (in adapter/out/persistence/) must implement Load{Domain}Port and Save{Domain}Port interfaces and delegate to JPA repositories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Load Port methods must use Optional for single object returns and avoid returning Response DTOs

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*Entity.java : Entity classes must use NoArgsConstructor(access = AccessLevel.PROTECTED), not Setter, and reference only IDs of other domains instead of direct entity references

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain Entity references are forbidden; only store ID fields (Column private Long {domain}Id), access other domains via UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/out/**Adapter.java : Persistence adapter classes must implement Port interfaces and delegate to Repository instances

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/RegisterSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/exception/**/*.java : Exception handling must use BusinessException, ErrorCode enums, and GlobalExceptionHandler in global/exception/ package

Applied to files:

  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/**Info.java : Info DTOs for cross-domain responses must use {Domain}Info naming pattern and be immutable records

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/SchoolInfo.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageRequest.java
🧬 Code graph analysis (5)
src/main/java/com/umc/product/organization/domain/School.java (5)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-59)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-45)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (19-45)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-46)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (19-66)
src/main/java/com/umc/product/organization/domain/Chapter.java (5)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-59)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-45)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-46)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (19-66)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-43)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (6)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-59)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-45)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (19-45)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-46)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (19-66)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-43)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (6)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-45)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (19-45)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-46)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (19-66)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-43)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (18-47)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (6)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-59)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (19-45)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-46)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (19-66)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-43)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (18-47)
🔇 Additional comments (35)
.gitignore (1)

239-239: 이 파일은 함수적 영향이 없습니다.

.gitignore 파일의 마지막 주석 라인만 변경되었으며, 이는 기능적 변화가 없습니다. 표준 Java/Spring Boot 프로젝트 설정은 적절해 보입니다.

src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java (1)

5-13: LGTM!

응답용 Info DTO로 적절하게 구성되어 있으며, 불변 record 패턴을 잘 따르고 있습니다.

src/main/java/com/umc/product/organization/application/port/in/UpdateSchoolUseCase.java (1)

6-8: 인터페이스 설계는 적절합니다.

  • 단일 책임 원칙(SRP)을 준수하여 하나의 use case만 정의하고 있습니다.
  • {Action}{Domain}UseCase 네이밍 패턴을 따르고 있습니다.
  • Entity가 아닌 Info DTO를 반환하여 도메인 객체 노출을 방지하고 있습니다.
src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java (2)

11-37: 에러 코드 패턴이 잘 구현되었습니다.

BaseCode 인터페이스를 구현하고 있으며, 전역 예외 처리 패턴과 일관성 있게 설계되었습니다. 각 에러 코드는 명확한 메시지를 가지고 있고, 검증 실패 상황에 적합한 BAD_REQUEST 상태 코드를 사용하고 있습니다.

Based on learnings, exception handling follows the correct pattern with ErrorCode enum and BaseCode interface.


32-32: enum 상수 뒤의 세미콜론 제거를 고려하세요.

마지막 enum 상수 뒤에 불필요한 세미콜론이 있습니다. 추가 멤버가 없는 경우 세미콜론은 생략 가능합니다.

🔎 제안하는 수정
     STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGAN-412", "스터디 그룹은 필수입니다."),
-    STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGAN-413", "스터디 그룹 멤버는 필수입니다.");
+    STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGAN-413", "스터디 그룹 멤버는 필수입니다.")
 
-    ;
-
     private final HttpStatus httpStatus;

Likely an incorrect or invalid review comment.

src/main/java/com/umc/product/organization/application/port/in/dto/SchoolSummary.java (1)

3-7: 간결하고 명확한 DTO 설계입니다.

불변성을 보장하는 record를 사용하여 inbound port의 응답 DTO를 잘 구현했습니다. 필드명도 명확하고 목적에 부합합니다.

src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java (1)

6-10: Outbound port 설계가 모범적입니다.

인터페이스 분리 원칙(ISP)에 따라 Load port를 별도로 분리했고, 다음 사항들이 잘 지켜졌습니다:

  • Optional을 사용한 단일 객체 반환
  • 기술 중립적인 메서드명 (findById, findByName)
  • 도메인 엔티티 반환
  • 올바른 패키지 위치 (application/port/out)

Based on learnings, this port follows all guidelines: ISP compliance, Optional usage for single objects, technology-neutral naming, and returns domain entities.

src/main/java/com/umc/product/organization/application/port/in/dto/SchoolDeleteSearchCondition.java (1)

3-7: 검색 조건 DTO가 간결하게 설계되었습니다.

불변 record를 사용하여 검색 조건을 명확히 표현했습니다.

다음 사항을 확인해주세요:

  • keywordchapterId가 null일 경우의 검색 동작이 명확한지 검증 (선택적 필터로 의도했다면 문제없습니다)
  • UseCase 레벨에서 적절한 null 처리 또는 Optional 사용이 되어있는지 확인
src/main/java/com/umc/product/organization/application/port/in/dto/DeletableSchoolSummary.java (1)

5-14: 삭제 가능한 학교 정보 DTO가 잘 설계되었습니다.

필요한 정보를 명확히 포함하고 있으며, 불변 record를 사용하여 안전한 데이터 전달을 보장합니다. 학교와 지부 정보를 함께 포함하여 클라이언트가 추가 조회 없이 필요한 정보를 얻을 수 있습니다.

src/main/java/com/umc/product/organization/application/port/out/LoadCentralOrganizationPort.java (1)

1-10: 헥사고날 아키텍처 원칙을 잘 따르고 있습니다!

Load 포트가 Interface Segregation Principle에 따라 적절히 분리되어 있고, findById 메서드가 Optional을 반환하며 기술 중립적인 네이밍을 사용하고 있습니다.

Based on learnings, Load Port는 단일 객체 반환 시 Optional을 사용하고, Load/Save 포트를 분리하며, 기술 중립적인 메서드명을 사용해야 합니다.

src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java (1)

1-9: Save 포트 설계가 적절합니다!

Interface Segregation Principle에 따라 Save 포트가 Load 포트와 분리되어 있으며, savedelete 메서드가 기술 중립적인 네이밍을 사용하고 도메인 엔티티를 반환합니다.

Based on learnings, Save 포트는 Load 포트와 분리되어야 하며, 기술 중립적인 메서드명을 사용하고 도메인 엔티티를 반환해야 합니다.

src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java (1)

1-9: Save 포트 설계가 일관성 있게 구현되었습니다!

SaveSchoolPort와 동일한 패턴으로 Interface Segregation Principle을 따르며, 기술 중립적인 네이밍과 도메인 엔티티 반환 규칙을 준수합니다.

Based on learnings, Save 포트는 Load 포트와 분리되어야 하며, 기술 중립적인 메서드명을 사용하고 도메인 엔티티를 반환해야 합니다.

src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java (1)

9-11: LGTM!

UseCase 인터페이스가 단일 책임 원칙을 잘 따르고 있으며, Get{Domain}UseCase 네이밍 패턴을 준수하고 있습니다. 반환 타입도 Entity가 아닌 DTO(PageResult<DeletableSchoolSummary>)를 사용하여 가이드라인에 부합합니다.

src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java (1)

7-11: LGTM!

Load{Domain}Port 네이밍 패턴을 준수하고, 단일 객체 반환에 Optional을 사용하며, 기술 중립적인 메서드명(findById, findAll)을 사용하여 Port Out 인터페이스 가이드라인을 잘 따르고 있습니다.

src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java (1)

8-13: LGTM!

Port 인터페이스가 가이드라인을 잘 준수하고 있습니다. Optional 사용, 기술 중립적인 메서드명, 도메인 엔티티 반환 등 모든 요구사항을 충족합니다.

src/main/java/com/umc/product/organization/domain/Chapter.java (1)

19-45: LGTM!

도메인 엔티티가 다른 엔티티들(CentralOrganization, School 등)과 일관된 패턴을 따르고 있습니다:

  • Builder 패턴을 통한 생성자 기반 상태 초기화
  • 생성 시점에 유효성 검증
  • Setter 없이 도메인 메서드를 통한 상태 변경
  • JPA를 위한 protected 기본 생성자

도메인 가이드라인에 부합합니다.

src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageRequest.java (1)

6-13: LGTM!

Record 타입 사용으로 불변성을 보장하고, 유효성 검증 어노테이션이 적절히 적용되어 있습니다. offset() 메서드에서 long 캐스팅으로 오버플로우를 방지한 점도 좋습니다.

주석에서 언급한 대로, 이 페이징 DTO는 여러 도메인에서 재사용될 수 있으므로 향후 common 패키지로 이동하는 것을 권장합니다.

src/main/java/com/umc/product/organization/application/port/in/dto/SchoolInfo.java (1)

5-13: LocalDate vs LocalDateTime 사용 검토 필요

createdAtupdatedAt 필드에 LocalDate를 사용하고 있습니다. 일반적으로 생성/수정 시간은 시간 정보까지 포함하는 LocalDateTime을 사용하는 경우가 많습니다.

의도적으로 날짜만 필요한 경우라면 현재 구현이 적절하지만, 시간 정보도 필요하다면 LocalDateTime으로 변경을 고려해 주세요.

src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)

18-47: LGTM!

도메인 엔티티가 프로젝트의 다른 엔티티들과 일관된 패턴을 따르고 있습니다:

  • Builder 패턴 기반 생성자 검증
  • Lazy fetch 전략 사용
  • Protected 기본 생성자

StudyGroup과의 양방향 매핑이 올바르게 설정되어 있습니다 (mappedBy = "studyGroup"이 필드명과 일치).

TODO 주석을 통해 향후 Challenger 추가 계획이 명시되어 있어 유지보수성에 도움이 됩니다.

src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java (1)

5-9: 인터페이스 설계가 헥사고날 아키텍처 원칙을 잘 따르고 있습니다.

Interface Segregation Principle에 따라 Save 포트를 Load 포트와 분리한 설계가 적절합니다. 메서드명도 기술 중립적(save, delete)이며, 도메인 엔티티를 직접 반환하고 있어 올바른 구조입니다.

PR 설명에서 언급하신 "인터페이스 수가 많아진 점"은 실제로 ISP를 올바르게 적용한 결과이며, 각 포트가 단일 책임을 가지도록 하여 유지보수성과 테스트 용이성을 높이는 좋은 설계 선택입니다.

Based on learnings: Port interfaces must be separated into Load and Save ports following ISP.

src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java (1)

5-9: 일관된 Save 포트 패턴을 따르고 있습니다.

다른 Save 포트들과 동일한 구조(save, delete 메서드)를 유지하여 일관성 있는 설계를 보여주고 있습니다. 도메인 엔티티를 직접 반환하고 기술 중립적인 메서드명을 사용하여 헥사고날 아키텍처 원칙을 준수하고 있습니다.

Based on learnings: Port Out interface method names must be technology-neutral.

src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java (1)

8-9: Save 포트 메서드 설계가 적절합니다.

기술 중립적인 메서드명과 도메인 엔티티 반환 타입을 사용하여 헥사고날 아키텍처 원칙을 잘 따르고 있습니다.

Based on learnings: Port interfaces must return domain entities or optionals.

src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java (1)

8-12: Load 포트가 올바른 반환 타입을 사용하고 있습니다.

단일 객체 조회(findById)는 Optional을 사용하고, 다중 객체 조회(findByStudyGroup)는 List를 사용하는 것이 적절합니다. Response DTO가 아닌 도메인 엔티티를 직접 반환하며, 메서드명도 기술 중립적으로 잘 설계되어 있습니다.

Based on learnings: Load Port methods must use Optional for single object returns and avoid returning Response DTOs.

src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java (1)

9-14: Load 포트 설계가 헥사고날 아키텍처 원칙을 완벽하게 준수하고 있습니다.

모든 단일 객체 조회 메서드(findById, findByChapterAndSchool)가 Optional을 반환하고, 컬렉션 조회 메서드(findAllByChapter)는 List를 반환하는 것이 올바릅니다. 메서드명이 기술 중립적이며 의도를 명확하게 표현하고 있고, 도메인 엔티티를 직접 반환하여 계층 간 책임이 명확히 분리되어 있습니다.

Based on learnings: Port interfaces must be defined in application/port/out/ package and return domain entities or optionals.

src/main/java/com/umc/product/organization/domain/School.java (1)

15-43: LGTM! 도메인 엔티티 설계가 우수합니다.

헥사고날 아키텍처 원칙을 잘 준수하고 있습니다:

  • Protected NoArgsConstructor와 private Builder를 통한 불변성 보장
  • logoImageId를 Long 타입으로 선언하여 크로스 도메인 참조 규칙 준수
  • 생성자에서의 도메인 검증 로직 적절히 구현
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)

18-45: LGTM! 일관된 도메인 모델 설계입니다.

Chapter 엔티티와 동일한 패턴을 따르며, Gisu와의 관계도 동일 도메인 내에서 적절히 구성되어 있습니다.

src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java (1)

5-9: LGTM! 아웃바운드 포트 설계가 적절합니다.

인터페이스 분리 원칙(ISP)을 준수하여 Save 포트를 별도로 분리했으며, 기술 중립적인 메서드명을 사용하고 있습니다.

src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java (1)

8-13: LGTM! 로드 포트 설계가 잘 되어 있습니다.

Optional 반환, 도메인 엔티티 사용, ISP 준수 등 모든 원칙을 잘 따르고 있습니다.

src/main/java/com/umc/product/organization/domain/StudyGroup.java (2)

41-42: CascadeType.ALL 사용에 대한 검증 필요

@OneToManyCascadeType.ALL이 설정되어 있어, StudyGroup 삭제 시 모든 연관된 StudyGroupMember도 함께 삭제됩니다. 이것이 의도된 동작이 맞는지 확인이 필요합니다.

만약 StudyGroup이 삭제되어도 StudyGroupMember는 보존되어야 한다면, cascade 옵션을 제거하거나 특정 타입만 선택적으로 지정하는 것을 고려해주세요:

-@OneToMany(mappedBy = "studyGroup", cascade = CascadeType.ALL)
+@OneToMany(mappedBy = "studyGroup", cascade = {CascadeType.PERSIST, CascadeType.MERGE})

또는 orphanRemoval도 함께 검토가 필요할 수 있습니다.


22-59: 전반적으로 좋은 도메인 설계입니다.

Builder 패턴, 검증 로직, protected 생성자 사용 등 도메인 주도 설계 원칙을 잘 따르고 있습니다. CascadeType 설정만 확인 부탁드립니다.

src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java (1)

5-9: 인터페이스 설계가 올바르게 구현되었습니다.

Interface Segregation Principle을 준수하여 Save 포트를 별도로 분리했고, 기술 중립적인 메서드명(save, delete)을 사용했으며, 도메인 엔티티를 반환하는 등 헥사고날 아키텍처 패턴을 정확히 따르고 있습니다.

Based on learnings, Port Out interfaces must be separated into Load and Save ports following Interface Segregation Principle.

src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)

18-45: 도메인 엔티티 설계가 우수합니다.

생성자 내 검증 로직, LAZY 페칭 전략, 불변성 보장(protected 기본 생성자, setter 없음) 등 모범 사례를 잘 따르고 있습니다. 빌더 패턴을 통한 객체 생성 시 필수 필드 검증도 적절합니다.

As per coding guidelines, state changes in Entity classes must be performed through domain methods, not via setters.

src/main/java/com/umc/product/organization/domain/Gisu.java (3)

62-65: 기간 포함 여부 로직의 경계 조건을 확인해주세요.

isInPeriod 메서드가 [startAt, endAt) 세맨틱(시작 포함, 종료 미포함)을 사용하고 있습니다. 비즈니스 요구사항에서 endAt 시점을 기간에 포함해야 하는지 확인이 필요합니다. 예를 들어, 기수 종료일이 2024-01-31 23:59:59라면 해당 시점도 기간에 포함되어야 할 수 있습니다.

현재 구현된 로직이 의도한 비즈니스 로직과 일치하는지 확인 부탁드립니다.


54-60: 도메인 메서드를 통한 상태 변경이 올바르게 구현되었습니다.

active()inactive() 메서드를 통해 엔티티 상태 변경을 캡슐화하여, setter를 직접 노출하지 않는 도메인 주도 설계 원칙을 잘 따르고 있습니다.

As per coding guidelines, state changes in Entity classes must be performed through domain methods, not via setters.


42-52: 검증 로직이 명확하고 적절합니다.

필수 필드 검증과 비즈니스 규칙(시작일이 종료일보다 앞서야 함) 검증을 생성자에서 수행하여 불변성과 일관성을 보장하고 있습니다. 예외 처리도 적절한 커스텀 예외를 사용하고 있습니다.

As per coding guidelines, meaningful custom exceptions should be used rather than generic RuntimeException.

LocalDate createdAt,
boolean isActive
) {

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

불필요한 빈 줄을 제거하세요.

record 본문에 빈 줄이 불필요합니다.

🔎 제안하는 수정
 public record DeletableSchoolSummary(
         Long schoolId,
         String schoolName,
         Long chapterId,
         String chapterName,
         LocalDate createdAt,
         boolean isActive
 ) {
-
 }
📝 Committable suggestion

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

Suggested change
public record DeletableSchoolSummary(
Long schoolId,
String schoolName,
Long chapterId,
String chapterName,
LocalDate createdAt,
boolean isActive
) {
}
🤖 Prompt for AI Agents
In
src/main/java/com/umc/product/organization/application/port/in/dto/DeletableSchoolSummary.java
around line 13, there is an unnecessary blank line inside the record body;
remove that extra empty line so the record has no blank line within its
declaration (keep formatting consistent with surrounding records).

Comment on lines +11 to +13
public long totalPages() {
return (totalCount + limit - 1) / limit;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

limit이 0일 때 ArithmeticException 발생 위험

totalPages() 메서드에서 limit으로 나누기 연산을 수행하는데, limit이 0인 경우 ArithmeticException(division by zero)이 발생합니다.

입력 검증을 추가하거나 0일 때의 처리 로직이 필요합니다.

🔎 제안하는 수정 방안
 public long totalPages() {
+    if (limit <= 0) {
+        return 0; // 또는 적절한 기본값 또는 예외 throw
+    }
     return (totalCount + limit - 1) / limit;
 }

또는 record의 compact constructor에서 유효성 검증:

 public record PageResult<T>(
         List<T> items,
         long totalCount,
         int page,
         int limit
 ) {
+    public PageResult {
+        if (limit <= 0) {
+            throw new IllegalArgumentException("limit must be greater than 0");
+        }
+        if (page < 0) {
+            throw new IllegalArgumentException("page must be non-negative");
+        }
+    }
+
     public long totalPages() {
         return (totalCount + limit - 1) / limit;
     }
 }
📝 Committable suggestion

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

Suggested change
public long totalPages() {
return (totalCount + limit - 1) / limit;
}
public long totalPages() {
if (limit <= 0) {
return 0;
}
return (totalCount + limit - 1) / limit;
}
🤖 Prompt for AI Agents
In
src/main/java/com/umc/product/organization/application/port/in/dto/paging/PageResult.java
around lines 11 to 13, totalPages() currently divides by limit and will throw
ArithmeticException when limit is 0; add input validation in the record's
compact constructor to require limit > 0 (throw IllegalArgumentException with a
clear message if not) so limit can never be zero, or alternatively guard
totalPages() to return 0 or handle the zero case explicitly—pick the constructor
validation approach and implement that check to prevent division by zero.


Optional<Chapter> findById(Long id);
List<Chapter> findAll();
List<Chapter> findAllByGisu(Gisu gisu);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

선택적 개선: 파라미터 타입 단순화 고려

findAllByGisu 메서드가 Gisu 엔티티를 직접 파라미터로 받고 있습니다. 동일 도메인 내에서는 문제없지만, Long gisuId로 변경하면 결합도를 더 낮출 수 있습니다.

-List<Chapter> findAllByGisu(Gisu gisu);
+List<Chapter> findAllByGisuId(Long gisuId);

현재 구현도 충분히 적절하므로, 팀 내 컨벤션에 따라 선택하시면 됩니다.

🤖 Prompt for AI Agents
In
src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
around line 12, the method currently accepts a Gisu entity (List<Chapter>
findAllByGisu(Gisu gisu)); change the parameter to a simple identifier to reduce
coupling: replace the signature with List<Chapter> findAllByGisuId(Long gisuId),
then update all implementations and callers to pass the gisuId (or fetch the
Gisu entity inside the implementing adapter/repository when needed), and ensure
any null/validation checks and mappings from gisuId to Gisu are handled in the
implementing layer.


public interface SaveChapterPort {


Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

불필요한 공백 라인을 제거하는 것을 권장합니다.

다른 Save 포트 인터페이스들(SaveGisuPort, SaveChapterSchoolPort)과의 일관성을 위해 불필요한 빈 줄을 제거하는 것이 좋습니다.

🔎 제안하는 수정
 public interface SaveChapterPort {
-

     Chapter save(Chapter chapter);
📝 Committable suggestion

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

Suggested change
public interface SaveChapterPort {
Chapter save(Chapter chapter);
void delete(Chapter chapter);
}
🤖 Prompt for AI Agents
In
src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
around line 7, remove the unnecessary blank line to match the
formatting/consistency of other Save* port interfaces (e.g., SaveGisuPort,
SaveChapterSchoolPort); simply delete the empty line so the file has no
extraneous blank lines and adheres to the project's interface formatting
conventions.

Comment on lines +37 to +39
// @ManyToOne(fetch = FetchType.LAZY)
// @JoinColumn(name = "group_leader_id")
// private Challenger challenger;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

TODO 항목 추적 지원

Challenger 도메인 추가가 예정되어 있는 것으로 보입니다. 이 작업을 별도 이슈로 추적하시겠습니까?

Also applies to: 45-46

🤖 Prompt for AI Agents
In src/main/java/com/umc/product/organization/domain/StudyGroup.java around
lines 37-39 and 45-46, the Challenger relationship is commented out but not
tracked; create a separate issue/ticket for adding the Challenger domain and
reference its ID here, then replace the commented-out block with a short TODO
comment including the issue/ticket reference and expected work (e.g., "TODO:
restore ManyToOne Challenger mapping — see ISSUE-123"), or alternatively add a
brief @Transient placeholder field with the TODO reference so intent is
preserved in code; ensure both locations include the same ticket reference and a
one-line note about who owns the task.

@kyeoungwoon이 처음에 올릴 때 Formatting 안 하고 올려서 그래요
죄송..
다음 타자는 @wlgusqkr에게 맡깁니다!
@kyeoungwoon
Copy link
Member

P1: StudyGroupStudyGroupMember를 aggregate 처리 했습니다. 추후 변경사항은 @wlgusqkr 이 진행합니다.

  • gisuCentralOrganizationChapter aggregate
  • ChapterChapterSchool aggregate
  • code review
  • 테스트 제작

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Fix all issues with AI Agents 🤖
In
@src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java:
- Around line 8-10: Add JavaDoc to the GetDeletableSchoolListUseCase interface
and its getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest)
method that documents behavior, parameters, return value, and failure modes;
explicitly declare the checked/unchecked exceptions the contract can throw
(e.g., IllegalArgumentException or a project-specific InvalidParameterException
when condition or pageRequest is null or when paging params are invalid
(negative/zero), and a ResourceNotFoundException or ChapterNotFoundException
when a referenced chapterId does not exist) so callers know expected error cases
and handling; ensure the JavaDoc lists each exceptional case and reference the
parameter types SchoolDeleteSearchCondition, PageRequest and the return type
PageResult<DeletableSchoolSummary> so implementors and callers can follow the
contract.
- Line 1: The GetDeletableSchoolListUseCase interface is placed in the wrong
package; move it from package com.umc.product.organization.application.port.in
to the query subpackage to follow hexagonal conventions. Update the package
declaration to com.umc.product.organization.application.port.in.query and
relocate the file into the corresponding directory, ensuring the interface name
GetDeletableSchoolListUseCase remains unchanged and any imports/usages elsewhere
are updated to the new package.

In @src/main/java/com/umc/product/organization/domain/ChapterSchool.java:
- Around line 27-33: Add a unique constraint at the entity level to prevent
duplicate (chapter, school) pairs: modify the ChapterSchool entity to include a
@Table annotation with a UniqueConstraint on the columnNames {"chapter_id",
"school_id"} and add the imports for jakarta.persistence.Table and
jakarta.persistence.UniqueConstraint; this ensures the ChapterSchool mapping
(fields chapter and school) enforces uniqueness at the DB level.

In @src/main/java/com/umc/product/organization/domain/Gisu.java:
- Around line 59-62: The current isInPeriod(LocalDateTime now) only checks
startAt/endAt and ignores the isActive flag; either make the intent explicit by
renaming it to isTimeInPeriod(LocalDateTime now) or add a new method
isActiveAndInPeriod(LocalDateTime now) that returns isActive && (now >= startAt
&& now < endAt) so callers don’t need to remember to check isActive separately;
update usages to call the new/renamed method and add a brief JavaDoc to clarify
behavior for startAt, endAt and isActive.

In @src/main/java/com/umc/product/organization/domain/School.java:
- Around line 30-36: The School builder's constructor uses a parameter named
"domain" while the field is "emailDomain", causing inconsistency; update the
@Builder private School constructor signature to accept "emailDomain" instead of
"domain", update the validate(...) call to pass emailDomain and assign
this.emailDomain = emailDomain so parameter and field names match throughout the
School constructor.

In @src/main/java/com/umc/product/organization/domain/StudyGroup.java:
- Around line 77-98: The current updateMembers method replaces all
StudyGroupMember entries by clearing studyGroupMembers and re-adding, which can
be inefficient for large lists; change updateMembers to compute a diff between
existing member challengerIds (from studyGroupMembers) and incoming
challengerIds, remove only members whose challengerId is absent (use
removeMember or collection remove) and add only new challengerIds via addMember,
while preserving existing members to avoid unnecessary deletes/inserts.

In
@src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java:
- Line 32: There is a stray duplicate semicolon causing a syntax error in the
enum constant STUDY_GROUP_MEMBER_NOT_FOUND of OrganizationErrorCode; remove the
extra semicolon so the line ends with a single semicolon (i.e., ensure the enum
constant declaration for STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND,
"ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.") uses only one trailing semicolon).
- Around line 12-32: The enum OrganizationErrorCode contains a misspelled
error-code prefix "ORGANIZAITON" in the message codes for constants like
GISU_REQUIRED, ORGAN_NAME_REQUIRED, SCHOOL_REQUIRED, CHAPTER_REQUIRED,
GISU_START_AT_REQUIRED, GISU_END_AT_REQUIRED, GISU_PERIOD_INVALID,
SCHOOL_NAME_REQUIRED, SCHOOL_DOMAIN_REQUIRED, STUDY_GROUP_NAME_REQUIRED,
STUDY_GROUP_LEADER_REQUIRED, STUDY_GROUP_REQUIRED, STUDY_GROUP_MEMBER_REQUIRED,
CHALLENGER_ID_REQUIRED, STUDY_GROUP_MEMBER_ALREADY_EXISTS, and
STUDY_GROUP_MEMBER_NOT_FOUND — update the second constructor argument for each
of these to use the correct prefix "ORGANIZATION-XXXX" (preserving the numeric
suffix), then rebuild and run tests and search the repo for any other
occurrences of "ORGANIZAITON" to fix similarly.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/main/java/com/umc/product/global/config/P6SpyConfig.java (2)

22-27: formatMessage 메서드: 불필요한 formatSql 호출 제거 필요

Line 24에서 formatSql 호출 결과를 변수에 할당하지 않아서 그 값이 버려집니다. Line 26에서 동일한 sql 인자로 다시 formatSql을 호출하므로, Line 24는 제거하거나 의도를 명확히 해야 합니다.

🔎 제안된 수정안
 @Override
 public String formatMessage(int connectionId, String now, long elapsed, String category,
                             String prepared, String sql, String url) {
-    sql = formatSql(category, sql);
     // [실행시간] | SQL 문법
     return String.format("[%s] | %d ms | %s", category, elapsed, formatSql(category, sql));
 }

29-46: formatSql 프라이빗 메서드: 입력 검증 및 경계값 처리 적절함

Null/빈 문자열 검증이 적절하게 처리되고 있으며, Locale.ROOT를 사용한 locale-insensitive 비교로 SQL 키워드 감지가 안정적입니다. 다만 현재 DDL 명령어 감지는 startsWith로 구현되어 있는데, 향후 유지보수성을 고려하면 상수 집합으로 추출하는 것도 좋은 리팩토링이 될 수 있습니다.

🔎 선택적 리팩토링: DDL 명령어 상수화
 public static class P6SpyFormatter implements MessageFormattingStrategy {
+    private static final Set<String> DDL_KEYWORDS = Set.of("create", "alter", "comment");
 
     @Override
     public String formatMessage(int connectionId, String now, long elapsed, String category,
                                 String prepared, String sql, String url) {
-        sql = formatSql(category, sql);
         // [실행시간] | SQL 문법
         return String.format("[%s] | %d ms | %s", category, elapsed, formatSql(category, sql));
     }
 
     private String formatSql(String category, String sql) {
         if (sql == null || sql.trim().isEmpty()) {
             return sql;
         }
 
         // Only format Statement, PreparedStatement
         if ("statement".equals(category)) {
             String trimmedSQL = sql.trim().toLowerCase(Locale.ROOT);
-            if (trimmedSQL.startsWith("create") || trimmedSQL.startsWith("alter")
-                    || trimmedSQL.startsWith("comment")) {
+            if (DDL_KEYWORDS.stream().anyMatch(trimmedSQL::startsWith)) {
                 sql = FormatStyle.DDL.getFormatter().format(sql);
             } else {
                 sql = FormatStyle.BASIC.getFormatter().format(sql);
             }
             return sql;
         }
         return sql;
     }
 }
src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java (1)

101-130: Line 128: getMostSpecificCause()의 null 안전성 검증 필요.

e.getMostSpecificCause().getMessage()가 NullPointerException을 발생시킬 수 있습니다. getMostSpecificCause() 자체가 null을 반환하거나, 반환된 객체의 getMessage()가 null일 수 있습니다.

🔎 권장 수정 방안
  return handleExceptionInternalFalse(
          e,
          CommonErrorCode.BAD_REQUEST,
          headers,
          HttpStatus.BAD_REQUEST,
          request,
-         simplifiedMessage + " - " + e.getMostSpecificCause().getMessage()
+         simplifiedMessage + (e.getMostSpecificCause() != null && e.getMostSpecificCause().getMessage() != null 
+             ? " - " + e.getMostSpecificCause().getMessage() 
+             : "")
  );

또는 더 간결하게:

  String detailMessage = Optional.ofNullable(e.getMostSpecificCause())
      .map(Throwable::getMessage)
      .map(msg -> " - " + msg)
      .orElse("");
  
  return handleExceptionInternalFalse(
          e,
          CommonErrorCode.BAD_REQUEST,
          headers,
          HttpStatus.BAD_REQUEST,
          request,
          simplifiedMessage + detailMessage
  );
src/main/java/com/umc/product/global/security/ApiAccessDeniedHandler.java (1)

27-33: 예외 메시지 노출로 인한 정보 유출 위험

accessDeniedException.getMessage()를 그대로 응답에 포함시키면 내부 구현 정보나 민감한 시스템 정보가 클라이언트에게 노출될 수 있습니다. 코딩 가이드라인에 따르면 예외 메시지에 민감 정보가 노출되지 않도록 해야 합니다.

🔎 제안하는 수정 방안

일반적인 메시지를 사용하거나 내부 로깅만 수행하도록 수정하세요:

-        ApiResponse<Object> body = ApiResponse.onFailure(
-                status.getCode(),
-                status.getMessage(),
-                accessDeniedException.getMessage()
-        );
+        // 내부 로깅은 유지하되, 클라이언트에는 일반적인 메시지만 전달
+        ApiResponse<Object> body = ApiResponse.onFailure(
+                status.getCode(),
+                status.getMessage(),
+                null  // 또는 "접근 권한이 없습니다" 같은 일반적인 메시지
+        );

또는 별도의 로깅 처리:

+        // 디버깅을 위한 내부 로깅
+        log.debug("Access denied: {}", accessDeniedException.getMessage());
+        
         ApiResponse<Object> body = ApiResponse.onFailure(
                 status.getCode(),
                 status.getMessage(),
-                accessDeniedException.getMessage()
+                null
         );
♻️ Duplicate comments (10)
src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java (1)

5-11: 이전 리뷰에서 지적된 사항들이 아직 수정되지 않았습니다.

다음 세 가지 문제가 여전히 남아있습니다:

  1. chapterId 필드 검증 누락: @NotNull 어노테이션이 필요합니다. Chapter는 School의 필수 연관관계로 보이므로 null 검증이 반드시 필요합니다.

  2. remark 필드 검증/명시 누락: 선택 필드라면 @Nullable 또는 주석으로 명시하고, 필수 필드라면 @NotBlank 추가가 필요합니다.

  3. 도메인 엔티티 변환 메서드 누락: Command 레코드는 toEntity() 또는 toDomain() 메서드를 포함해야 합니다. 이를 통해 DTO와 엔티티 간 변환 로직이 명확해지고 중복이 방지됩니다.

🔎 제안하는 수정사항
+import com.umc.product.organization.domain.School;
+import com.umc.product.organization.domain.Chapter;
+import jakarta.validation.constraints.NotNull;
+
 public record RegisterSchoolCommand(
         @NotBlank
         String schoolName,
+        @NotNull
         Long chapterId,
         String remark  // 선택 필드인 경우
 ) {
+    public School toEntity(Chapter chapter) {
+        return School.builder()
+                .schoolName(schoolName)
+                .chapter(chapter)
+                .remark(remark)
+                .build();
+    }
 }

Based on learnings, Command 레코드는 불변성을 갖추고 검증 어노테이션과 변환 메서드를 포함해야 합니다.

src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java (2)

11-12: chapterId 타입 불일치 문제

UpdateSchoolCommandchapterIdString으로 선언되어 있으나, UpdateSchoolInfo에서는 Long 타입으로 정의되어 있습니다. ID 필드는 일관되게 Long 타입을 사용해야 하며, 타입 불일치는 런타임 시 변환 오류나 데이터 불일치를 유발할 수 있습니다.

🔎 제안된 수정
        @NotNull
-       String chapterId,
+       Long chapterId,

6-16: Command record에 변환 메서드 추가 필요

코딩 가이드라인에 따르면 Command record는 도메인 엔티티로의 변환 로직을 캡슐화하기 위해 toEntity() 또는 toCommand() 변환 메서드를 포함해야 합니다. 이를 통해 DTO에서 도메인 객체로의 변환 책임을 명확히 할 수 있습니다.

🔎 제안된 구현 예시
 public record UpdateSchoolCommand(
         @NotNull
         Long schoolId,
         @NotBlank
         String schoolName,
         @NotNull
-        String chapterId,
+        Long chapterId,
         String remark
 
-) {
+) {
+    public School toEntity() {
+        return School.builder()
+                .id(schoolId)
+                .name(schoolName)
+                .chapterId(chapterId)
+                .remark(remark)
+                .build();
+    }
 }

참고: 실제 구현 시 도메인 엔티티의 생성자 또는 빌더 패턴에 맞춰 조정이 필요합니다.

Based on learnings, Command records should include conversion method.

src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java (4)

1-1: 패키지 구조가 헥사고날 아키텍처 규칙을 위반합니다.

학습된 가이드라인에 따르면, Delete 작업은 command 유형이므로 이 UseCase 인터페이스는 application/port/in/command/ 패키지에 위치해야 합니다.

Based on learnings, UseCase interfaces must be defined in application/port/in/{command|query}/ packages.


5-9: UseCase 인터페이스의 단일 책임 원칙 위반

학습 가이드라인에 따르면 UseCase 인터페이스는 인터페이스당 하나의 유스케이스만 포함해야 합니다. 현재 DeleteSchoolUseCase는 두 가지 별도의 유스케이스를 포함하고 있어 단일 책임 원칙을 위반합니다.

권장 방안:

  • DeleteSchoolUseCase (단일 삭제)
  • DeleteSchoolsUseCase (배치 삭제)

두 개의 별도 인터페이스로 분리하여 각각을 application/port/in/command/ 패키지에 배치하시기 바랍니다.

Based on learnings, UseCase (Port In) interfaces should have single responsibility with one use case per interface.


8-8: 메서드 명명 개선 필요

deleteAll(List<Long> schoolIds) 메서드명은 "시스템의 모든 학교 삭제"로 오해될 수 있습니다. 실제 동작은 지정된 ID 목록만 삭제하므로 deleteByIds로 명명하는 것이 명확합니다.

🔎 제안된 수정사항
-    void deleteAll(List<Long> schoolIds);
+    void deleteByIds(List<Long> schoolIds);

5-9: JavaDoc 문서화와 예외 처리 명세가 누락되었습니다.

코딩 가이드라인에 따르면, 예외 처리가 적절히 정의되고 의미 있는 커스텀 예외를 사용해야 합니다. 다음 항목들을 추가하여 인터페이스의 계약을 명확히 해주세요:

  1. JavaDoc 문서: 각 메서드의 동작, 파라미터, 예외 상황 설명
  2. 예외 처리 명세:
    • schoolId가 null인 경우
    • 존재하지 않는 학교 ID인 경우
    • schoolIds 리스트가 null 또는 비어있는 경우
    • 삭제 권한이 없는 경우
    • 삭제할 수 없는 상태의 학교인 경우 (예: 연관된 데이터 존재)

As per coding guidelines, 예외가 적절히 처리되었는지 확인하고 의미 있는 커스텀 예외를 사용해야 합니다.

src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java (2)

8-8: 인터페이스명 오타 수정 필요

GetSchoolUserCase가 아니라 GetSchoolUseCase로 수정해야 합니다. 파일명도 함께 변경이 필요합니다.


8-12: UseCase 인터페이스의 단일 책임 원칙 위반

하나의 UseCase 인터페이스에 getAllgetById 두 개의 메서드가 정의되어 있습니다. 헥사고날 아키텍처의 UseCase 패턴에 따라 GetSchoolListUseCaseGetSchoolDetailUseCase로 분리를 권장합니다.

Based on learnings, UseCase 인터페이스는 단일 책임을 가져야 합니다.

src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java (1)

5-11: 불필요한 공백 라인이 있습니다.

Line 6-7에 불필요한 빈 줄이 있습니다. 다른 Save 포트 인터페이스들과의 일관성을 위해 제거하는 것이 좋습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c48dd16 and 60bf9f7.

📒 Files selected for processing (38)
  • src/main/java/com/umc/product/global/config/P6SpyConfig.java
  • src/main/java/com/umc/product/global/config/SecurityConfig.java
  • src/main/java/com/umc/product/global/config/WebMvcConfig.java
  • src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java
  • src/main/java/com/umc/product/global/security/ApiAccessDeniedHandler.java
  • src/main/java/com/umc/product/global/security/ApiAuthenticationEntryPoint.java
  • src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java
  • src/main/java/com/umc/product/global/security/JwtAuthenticationFilter.java
  • src/main/java/com/umc/product/global/security/JwtTokenProvider.java
  • src/main/java/com/umc/product/global/security/UserPrincipal.java
  • src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
🧰 Additional context used
📓 Path-based instructions (11)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/global/security/ApiAccessDeniedHandler.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/global/security/ApiAuthenticationEntryPoint.java
  • src/main/java/com/umc/product/global/config/P6SpyConfig.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/global/security/UserPrincipal.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/global/security/JwtTokenProvider.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/global/config/WebMvcConfig.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
  • src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/global/security/JwtAuthenticationFilter.java
  • src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/global/config/SecurityConfig.java
**/application/port/out/**Port.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/out/**Port.java: Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle
Load Port methods must use Optional for single object returns and avoid returning Response DTOs
Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
**/*application/port/out/**Port.java

📄 CodeRabbit inference engine (CLAUDE.md)

Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
  • src/main/java/com/umc/product/global/security/ApiAccessDeniedHandler.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/global/security/ApiAuthenticationEntryPoint.java
  • src/main/java/com/umc/product/global/config/P6SpyConfig.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/global/security/UserPrincipal.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/global/security/JwtTokenProvider.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/global/config/WebMvcConfig.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
  • src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/global/security/JwtAuthenticationFilter.java
  • src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/global/config/SecurityConfig.java
**/domain/**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

State changes in Entity classes must be performed through domain methods, not via setters

Files:

  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
**/application/port/in/**UseCase.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/in/**UseCase.java: UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern
UseCase interfaces should return Info/DTO objects, not Entity objects directly

Files:

  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
**/*application/port/in/**/*{Command,Query}.java

📄 CodeRabbit inference engine (CLAUDE.md)

Command and Query records should be immutable with @NotNull validation and include conversion method toCommand() or toEntity()

Files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
**/*global/security/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Security configuration must use JWT (io.jsonwebtoken 0.12.5) with JwtTokenProvider, JwtAuthenticationFilter in global/security/ package; use @public and @currentuser annotations for method security

Files:

  • src/main/java/com/umc/product/global/security/ApiAccessDeniedHandler.java
  • src/main/java/com/umc/product/global/security/ApiAuthenticationEntryPoint.java
  • src/main/java/com/umc/product/global/security/UserPrincipal.java
  • src/main/java/com/umc/product/global/security/JwtTokenProvider.java
  • src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
  • src/main/java/com/umc/product/global/security/JwtAuthenticationFilter.java
  • src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java
**/*global/config/**Config.java

📄 CodeRabbit inference engine (CLAUDE.md)

Spring configuration files must be in global/config/ package and use @configuration annotation

Files:

  • src/main/java/com/umc/product/global/config/P6SpyConfig.java
  • src/main/java/com/umc/product/global/config/WebMvcConfig.java
  • src/main/java/com/umc/product/global/config/SecurityConfig.java
**/*global/exception/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Exception handling must use BusinessException, ErrorCode enums, and GlobalExceptionHandler in global/exception/ package

Files:

  • src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java
🧠 Learnings (28)
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/out/persistence/**PersistenceAdapter.java : Persistence adapters (in adapter/out/persistence/) must implement Load{Domain}Port and Save{Domain}Port interfaces and delegate to JPA repositories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/SaveChapterPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*Entity.java : Entity classes must use NoArgsConstructor(access = AccessLevel.PROTECTED), not Setter, and reference only IDs of other domains instead of direct entity references

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain Entity references are forbidden; only store ID fields (Column private Long {domain}Id), access other domains via UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*Test.java : Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : Controller classes must only depend on UseCase interfaces, never directly on Repository or Service implementations

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain communication from services must use Query UseCase interfaces (GetUserInfoUseCase, GetOrganizationUseCase) for accessing other domains, not direct repository access

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Service classes must not directly access other domain's Repository instances; use other domain's Query UseCase instead

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/GetSchoolUserCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/service/**/*Service.java : Service classes implementing usecases must be in application/service/{command|query}/ packages; command services use {Domain}CommandService, query services use {Domain}QueryService

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/*.java : Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/DeleteSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Load Port methods must use Optional for single object returns and avoid returning Response DTOs

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/dto/RegisterSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Controllers must not return Entity objects directly; always convert to Response DTO to prevent domain leaking

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/security/**/*.java : Security configuration must use JWT (io.jsonwebtoken 0.12.5) with JwtTokenProvider, JwtAuthenticationFilter in global/security/ package; use Public and CurrentUser annotations for method security

Applied to files:

  • src/main/java/com/umc/product/global/security/ApiAccessDeniedHandler.java
  • src/main/java/com/umc/product/global/security/ApiAuthenticationEntryPoint.java
  • src/main/java/com/umc/product/global/security/UserPrincipal.java
  • src/main/java/com/umc/product/global/security/JwtTokenProvider.java
  • src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
  • src/main/java/com/umc/product/global/security/JwtAuthenticationFilter.java
  • src/main/java/com/umc/product/global/security/CustomAuthorizationManager.java
  • src/main/java/com/umc/product/global/config/SecurityConfig.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/config/**Config.java : Spring configuration files must be in global/config/ package and use Configuration annotation

Applied to files:

  • src/main/java/com/umc/product/global/config/WebMvcConfig.java
  • src/main/java/com/umc/product/global/config/SecurityConfig.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/exception/**/*.java : Exception handling must use BusinessException, ErrorCode enums, and GlobalExceptionHandler in global/exception/ package

Applied to files:

  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : Authentication information must be obtained from AuthenticationPrincipal SecurityUser parameter, not from Request body

Applied to files:

  • src/main/java/com/umc/product/global/security/annotation/CurrentUser.java
  • src/main/java/com/umc/product/global/security/JwtAuthenticationFilter.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : Controller methods must validate request bodies using Valid annotation and request parameter constraints

Applied to files:

  • src/main/java/com/umc/product/global/exception/GlobalExceptionHandler.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/out/**Adapter.java : Persistence adapter classes must implement Port interfaces and delegate to Repository instances

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/LoadSchoolPort.java
🧬 Code graph analysis (6)
src/main/java/com/umc/product/organization/domain/School.java (4)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (6)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (2)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/global/security/ApiAuthenticationEntryPoint.java (1)
src/main/java/com/umc/product/temp/controller/TempController.java (1)
  • RequiredArgsConstructor (15-41)
src/main/java/com/umc/product/organization/domain/Gisu.java (3)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)

@@ -0,0 +1,11 @@
package com.umc.product.organization.application.port.in;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

패키지 구조가 헥사고날 아키텍처 규칙을 위반합니다.

학습된 가이드라인에 따르면, query 유형의 UseCase(Get으로 시작)는 application/port/in/query/ 패키지에 위치해야 합니다.

Based on learnings, UseCase interfaces must be defined in application/port/in/{command|query}/ packages; query usecases use Get{Domain}UseCase pattern.

🔎 제안된 수정사항
-package com.umc.product.organization.application.port.in;
+package com.umc.product.organization.application.port.in.query;
📝 Committable suggestion

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

Suggested change
package com.umc.product.organization.application.port.in;
package com.umc.product.organization.application.port.in.query;
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
around line 1, The GetDeletableSchoolListUseCase interface is placed in the
wrong package; move it from package
com.umc.product.organization.application.port.in to the query subpackage to
follow hexagonal conventions. Update the package declaration to
com.umc.product.organization.application.port.in.query and relocate the file
into the corresponding directory, ensuring the interface name
GetDeletableSchoolListUseCase remains unchanged and any imports/usages elsewhere
are updated to the new package.

Comment on lines 8 to 10
public interface GetDeletableSchoolListUseCase {
PageResult<DeletableSchoolSummary> getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

JavaDoc 문서화와 예외 처리 명세가 누락되었습니다.

코딩 가이드라인에 따르면, 예외 처리가 적절히 정의되어야 합니다. 다음 항목들을 추가하여 인터페이스의 계약을 명확히 해주세요:

  1. JavaDoc 문서: 메서드의 동작, 파라미터, 반환값, 예외 상황 설명
  2. 예외 처리 명세:
    • condition이 null인 경우
    • pageRequest가 null인 경우
    • 잘못된 페이징 파라미터인 경우 (음수, 0 등)
    • chapterId가 존재하지 않는 경우

As per coding guidelines, 예외가 적절히 처리되었는지 확인하고 의미 있는 커스텀 예외를 사용해야 합니다.

🔎 제안된 수정사항
+/**
+ * 삭제 가능한 학교 목록 조회 UseCase
+ * <p>
+ * 검색 조건과 페이징 정보에 따라 삭제 가능한 학교 목록을 조회하는 인바운드 포트입니다.
+ */
 public interface GetDeletableSchoolListUseCase {
+    /**
+     * 삭제 가능한 학교 목록을 페이징 조회합니다.
+     *
+     * @param condition 학교 검색 조건 (키워드, 지부 ID 등)
+     * @param pageRequest 페이징 요청 정보 (페이지 번호, 페이지 크기)
+     * @return 삭제 가능한 학교 요약 정보 목록과 페이징 메타데이터
+     * @throws IllegalArgumentException condition 또는 pageRequest가 null인 경우, 또는 잘못된 페이징 파라미터인 경우
+     * @throws ChapterNotFoundException chapterId가 존재하지 않는 경우
+     */
     PageResult<DeletableSchoolSummary> getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest);
 
 }
📝 Committable suggestion

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

Suggested change
public interface GetDeletableSchoolListUseCase {
PageResult<DeletableSchoolSummary> getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest);
/**
* 삭제 가능한 학교 목록 조회 UseCase
* <p>
* 검색 조건과 페이징 정보에 따라 삭제 가능한 학교 목록을 조회하는 인바운드 포트입니다.
*/
public interface GetDeletableSchoolListUseCase {
/**
* 삭제 가능한 학교 목록을 페이징 조회합니다.
*
* @param condition 학교 검색 조건 (키워드, 지부 ID )
* @param pageRequest 페이징 요청 정보 (페이지 번호, 페이지 크기)
* @return 삭제 가능한 학교 요약 정보 목록과 페이징 메타데이터
* @throws IllegalArgumentException condition 또는 pageRequest가 null인 경우, 또는 잘못된 페이징 파라미터인 경우
* @throws ChapterNotFoundException chapterId가 존재하지 않는 경우
*/
PageResult<DeletableSchoolSummary> getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest);
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/in/GetDeletableSchoolListUseCase.java
around lines 8-10, Add JavaDoc to the GetDeletableSchoolListUseCase interface
and its getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest)
method that documents behavior, parameters, return value, and failure modes;
explicitly declare the checked/unchecked exceptions the contract can throw
(e.g., IllegalArgumentException or a project-specific InvalidParameterException
when condition or pageRequest is null or when paging params are invalid
(negative/zero), and a ResourceNotFoundException or ChapterNotFoundException
when a referenced chapterId does not exist) so callers know expected error cases
and handling; ensure the JavaDoc lists each exceptional case and reference the
parameter types SchoolDeleteSearchCondition, PageRequest and the return type
PageResult<DeletableSchoolSummary> so implementors and callers can follow the
contract.

Comment on lines 59 to 62
public boolean isInPeriod(LocalDateTime now) {
return (now.isEqual(startAt) || now.isAfter(startAt))
&& now.isBefore(endAt);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find and read the Gisu.java file
find . -type f -name "Gisu.java" | head -1 | xargs cat -n

Repository: UMC-PRODUCT/umc-product-server

Length of output: 2410


🏁 Script executed:

#!/bin/bash
# Search for usage of isInPeriod method
rg "isInPeriod" -A 3 -B 3

Repository: UMC-PRODUCT/umc-product-server

Length of output: 696


🏁 Script executed:

#!/bin/bash
# Search for isActive field and related methods in the domain directory
rg "isActive" src/main/java/com/umc/product/organization/domain/ -A 2 -B 2

Repository: UMC-PRODUCT/umc-product-server

Length of output: 1802


🏁 Script executed:

#!/bin/bash
# Search all files for isInPeriod usage
rg "isInPeriod" -B 5 -A 5 --type java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 911


🏁 Script executed:

#!/bin/bash
# Also search for any method that combines isActive check with period checks
rg "isActive.*startAt|isActive.*endAt|isInPeriod.*isActive" -B 3 -A 3 --type java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 727


🏁 Script executed:

#!/bin/bash
# Check if there's a service layer that uses Gisu
find . -path "*/application/service*" -name "*Gisu*" -type f

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Broader search for Gisu usage in application layer
rg "new Gisu|Gisu\(" src/main/java/com/umc/product/organization/application/ -A 2 -B 2

Repository: UMC-PRODUCT/umc-product-server

Length of output: 492


🏁 Script executed:

#!/bin/bash
# Search for .isInPeriod( in entire repository
rg "\.isInPeriod\(" -B 5 -A 5

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Search for any reference to isInPeriod in test files
find . -path "*/test/*" -name "*.java" | xargs rg "isInPeriod" -B 3 -A 3

Repository: UMC-PRODUCT/umc-product-server

Length of output: 58


🏁 Script executed:

#!/bin/bash
# Check repository structure to understand the full context
ls -la src/main/java/com/umc/product/organization/

Repository: UMC-PRODUCT/umc-product-server

Length of output: 355


🏁 Script executed:

#!/bin/bash
# More comprehensive search - look in all file types
rg "isInPeriod" -i

Repository: UMC-PRODUCT/umc-product-server

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Check if there are any TODO or FIXME comments referencing this
rg "TODO|FIXME|NOTE" src/main/java/com/umc/product/organization/domain/Gisu.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


기수의 활성화 상태를 함께 고려할 것을 권장합니다.

isInPeriod() 메서드는 현재 기간(startAt ~ endAt)만 체크하고 isActive 플래그를 고려하지 않습니다. 기수는 isActive 필드로 활성화 상태를 관리하지만, 메서드 이름만으로는 활성화 상태도 확인해야 한다는 것이 명확하지 않아 호출 시 두 조건을 모두 확인해야 하는 부담이 있습니다.

메서드의 책임을 명확히 하기 위해 다음 중 하나를 고려해보세요:

  • 기간 + 활성화 상태를 함께 확인하는 메서드 생성: isActiveAndInPeriod()로 분리하여 의도를 명확히 함
  • 현재 설계 유지 시: 메서드 이름을 isTimeInPeriod()로 변경하거나 JavaDoc을 추가하여 활성화 상태는 별도로 확인해야 함을 명시
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/Gisu.java around lines
59-62, The current isInPeriod(LocalDateTime now) only checks startAt/endAt and
ignores the isActive flag; either make the intent explicit by renaming it to
isTimeInPeriod(LocalDateTime now) or add a new method
isActiveAndInPeriod(LocalDateTime now) that returns isActive && (now >= startAt
&& now < endAt) so callers don’t need to remember to check isActive separately;
update usages to call the new/renamed method and add a brief JavaDoc to clarify
behavior for startAt, endAt and isActive.

Comment on lines +77 to +98
/**
* 스터디 그룹에서 멤버 제거
*
* @param challengerId 제거할 챌린저 ID
* @throws BusinessException 멤버를 찾을 수 없는 경우
*/
public void removeMember(Long challengerId) {
StudyGroupMember member = findMemberByChallengerId(challengerId);
studyGroupMembers.remove(member);
}

/**
* 스터디 그룹 멤버 목록 전체 교체
*
* @param challengerIds 새로운 멤버 ID 목록
*/
public void updateMembers(List<Long> challengerIds) {
studyGroupMembers.clear();
if (challengerIds != null) {
challengerIds.forEach(this::addMember);
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

멤버 관리 메서드들이 적절하게 구현되었습니다.

  • removeMember: 멤버 조회 및 제거 로직 명확
  • updateMembers: 전체 교체 시 clear() 후 재추가로 일관성 유지
  • orphanRemoval 설정으로 삭제된 멤버 자동 정리
  • null 체크로 방어적 프로그래밍 실천

선택적 최적화 제안:

대량의 멤버가 있고 일부만 변경되는 경우, updateMembers의 전체 clear + 재추가 방식은 비효율적일 수 있습니다. 필요시 차분(diff) 기반 업데이트를 고려할 수 있습니다.

🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/StudyGroup.java around
lines 77-98, The current updateMembers method replaces all StudyGroupMember
entries by clearing studyGroupMembers and re-adding, which can be inefficient
for large lists; change updateMembers to compute a diff between existing member
challengerIds (from studyGroupMembers) and incoming challengerIds, remove only
members whose challengerId is absent (use removeMember or collection remove) and
add only new challengerIds via addMember, while preserving existing members to
avoid unnecessary deletes/inserts.

STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0013", "스터디 그룹 멤버는 필수입니다."),
CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0014", "챌린저 ID는 필수입니다."),
STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0015", "이미 존재하는 스터디 그룹 멤버입니다."),
STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

세미콜론 중복으로 인한 문법 오류

Line 32에서 ;; (세미콜론 두 개)가 있어 컴파일 오류가 발생합니다.

🔎 수정 제안
-    STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
+    STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZATION-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");
📝 Committable suggestion

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

Suggested change
STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZATION-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
around line 32, There is a stray duplicate semicolon causing a syntax error in
the enum constant STUDY_GROUP_MEMBER_NOT_FOUND of OrganizationErrorCode; remove
the extra semicolon so the line ends with a single semicolon (i.e., ensure the
enum constant declaration for STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND,
"ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.") uses only one trailing semicolon).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 25

Fix all issues with AI Agents 🤖
In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java:
- Around line 5-10: The class CreateSchoolUseCaseRequest should follow the
project's Command DTO naming convention: rename the record
CreateSchoolUseCaseRequest to CreateSchoolCommand (and update the filename and
all references/usages), keep it as an immutable Java record, preserve the
validation annotations (e.g., @NotBlank on schoolName) and the same fields
(schoolName, chapterId, remark), and update any constructors/usages in
services/controllers/ports that reference CreateSchoolUseCaseRequest to use
CreateSchoolCommand.

In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java:
- Around line 11-12: The chapterId field in UpdateSchoolUseCaseRequest is
declared as String with @NotNull but the Chapter entity uses a Long id; either
change the chapterId field type to Long and keep @NotNull (update
constructor/getter/setter and any mapping/code using UpdateSchoolUseCaseRequest
accordingly), or if you intend to keep String, replace @NotNull with @NotBlank
to reject empty strings and import javax.validation.constraints.NotBlank; ensure
all code paths that construct or consume UpdateSchoolUseCaseRequest are updated
to use the chosen type and validation annotation.

In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java:
- Around line 1-7: The SchoolDeleteSearchCondition record is in the dto.response
package but is an input/search parameter for SchoolQueryUseCase.getList(); move
this type to an input/request or condition package (recommended:
application.port.in.dto.request or application.port.in.dto.condition) and update
its package declaration accordingly so callers like SchoolQueryUseCase.getList()
import the type from the new package; ensure any references/imports to
SchoolDeleteSearchCondition are updated across the codebase.

In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java:
- Around line 5-13: The SchoolInfo record declares createdAt and updatedAt as
LocalDate which loses time precision; update the SchoolInfo record to use
java.time.LocalDateTime for those two fields (createdAt and updatedAt) and
adjust imports, any factory/mapping code that constructs SchoolInfo (e.g.,
mappers, constructors, or service methods that populate SchoolInfo) to pass
LocalDateTime values instead of LocalDate; ensure any
serialization/deserialization or DTO conversions that expect date-only values
are updated to handle LocalDateTime consistently.

In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolSummary.java:
- Around line 1-7: The SchoolSummary record is placed under the command package
but is used as the response type for the query use case
(SchoolQueryUseCase.getAll()); move the SchoolSummary record to a
query-appropriate package (e.g.,
query.organization.application.port.in.dto.response) or a shared common DTO
package if used by both sides, update its package declaration accordingly, and
adjust any imports/usages (including SchoolQueryUseCase.getAll() return type
references and other callers) to the new package so compilation and imports
remain consistent.

In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/UpdateSchoolInfo.java:
- Around line 5-13: UpdateSchoolInfo is unused across the codebase yet is
imported in SchoolCommandUseCase and lives in the wrong layer; either remove it
entirely (and remove the unused import from SchoolCommandUseCase) or convert it
into a proper response DTO by moving the record to the adapter/in/*/dto/response
package and adding a static from(domainInfo) factory method that maps domain
fields to the record's fields (ensure the symbol UpdateSchoolInfo contains
static from(...) and update any imports/usages accordingly).

In
@src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java:
- Line 14: Change the use-case to return an Info/DTO: restore the missing import
for UpdateSchoolInfo and change the method signature in SchoolCommandUseCase
from void update(UpdateSchoolUseCaseRequest request) to UpdateSchoolInfo
update(UpdateSchoolUseCaseRequest request); then update all implementations of
SchoolCommandUseCase (and any callers/tests) to build and return an
UpdateSchoolInfo instance representing the updated school state instead of
returning void.
- Line 4: Remove the unused import of UpdateSchoolInfo from the
SchoolCommandUseCase interface: locate the import line for
com.umc.product.command.organization.application.port.in.dto.response.UpdateSchoolInfo
and delete it since the update() method in SchoolCommandUseCase returns void and
does not reference UpdateSchoolInfo anywhere.
- Around line 10-11: Remove the extra blank line between the interface
declaration and its first method in SchoolCommandUseCase: open the interface
(SchoolCommandUseCase) and delete the consecutive empty line(s) immediately
following the "interface SchoolCommandUseCase" header so the first method
declaration appears directly after the interface line.

In
@src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java:
- Around line 6-13: CentralOrganizationCommandPort currently mixes read and
write operations and violates ISP; split it into two ports: create
LoadCentralOrganizationPort with Optional<CentralOrganization> findById(Long id)
(move the current findById) and create SaveCentralOrganizationPort with
CentralOrganization save(CentralOrganization organization) and void
delete(CentralOrganization organization) (move save/delete), update any classes
referencing CentralOrganizationCommandPort to depend on the appropriate new
interfaces (LoadCentralOrganizationPort or SaveCentralOrganizationPort) and
remove or deprecate CentralOrganizationCommandPort.
- Line 14: Remove the unnecessary blank line inside the
CentralOrganizationCommandPort interface body; open
CentralOrganizationCommandPort and delete the extra empty line between the
interface declaration and its members so the interface has no stray blank lines
in its body.

In
@src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java:
- Around line 5-11: ChapterCommandPort currently combines save and delete
operations, violating the Interface Segregation Principle; split this port into
two focused out-ports by extracting the save(...) method into a SaveChapterPort
interface (e.g., SaveChapterPort with Chapter save(Chapter)) and the delete(...)
method into a DeleteChapterPort interface (e.g., DeleteChapterPort with void
delete(Chapter)); update any consumers/implementations to depend on the new
SaveChapterPort and DeleteChapterPort instead of ChapterCommandPort and remove
the original ChapterCommandPort.

In
@src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java:
- Around line 5-10: Rename the port interface GisuCommandPort to SaveGisuPort to
follow the project's naming convention (Save{Domain}Port from CLAUDE.md); update
all references/usages of GisuCommandPort accordingly and keep the existing
methods save(Gisu gisu) and void delete(Gisu gisu) in the renamed interface, or
if you prefer stricter segregation later, split them into SaveGisuPort.save(...)
and DeleteGisuPort.delete(...) and update dependent code to use the new
interface names.

In
@src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java:
- Around line 5-10: SchoolCommandPort violates ISP by combining save and delete;
split it into separate save and delete ports. Create a SaveSchoolPort with the
School save(School school) method and a DeleteSchoolPort with void delete(School
school), replace usages of SchoolCommandPort with the appropriate new
interfaces, and update any adapters/implementations that currently implement
SchoolCommandPort to implement SaveSchoolPort and/or DeleteSchoolPort
accordingly (mirror the pattern used by ChapterCommandPort if present).

In
@src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java:
- Around line 5-9: StudyGroupCommandPort violates ISP by combining load/save
responsibilities; split it into two focused ports: define StudyGroupLoadPort
with load/read methods (e.g., findById/exists as needed) and StudyGroupSavePort
containing the save(StudyGroup) and delete(StudyGroup) operations; update any
usages to depend on the appropriate port (replace references to
StudyGroupCommandPort with StudyGroupSavePort where persisting is required and
with StudyGroupLoadPort where reads are required) and remove or deprecate
StudyGroupCommandPort after refactoring.

In @src/main/java/com/umc/product/command/organization/domain/School.java:
- Around line 15-47: School currently exposes only getters and no way to change
state after creation; add domain methods on the School entity (e.g., updateName,
updateEmailDomain, updateLogoImage) that perform the same validations as the
existing validate(String name, String domain) and throw BusinessException with
OrganizationErrorCode.SCHOOL_NAME_REQUIRED or SCHOOL_DOMAIN_REQUIRED when inputs
are invalid, then update the corresponding fields (name, emailDomain,
logoImageId); keep setters out and use these methods wherever
UpdateSchoolUseCaseRequest is applied.

In @src/main/java/com/umc/product/command/organization/domain/StudyGroup.java:
- Around line 68-75: The addMember(Long challengerId) method currently lets a
null challengerId reach StudyGroupMember.builder() causing a later exception;
add a fail-fast null check at the start of StudyGroup.addMember (e.g., throw
IllegalArgumentException or use Objects.requireNonNull) before calling
validateMemberNotExists and before creating StudyGroupMember so the Aggregate
Root explicitly validates challengerId and yields a clear error message; update
the message to mention challengerId is required and keep validateMemberNotExists
and StudyGroupMember.builder usage unchanged.
- Around line 105-110: The name validation in updateName duplicates the
constructor's validate logic; extract the check into a single helper method
(e.g., validateName or ensureValidName) and have both the constructor's validate
method and StudyGroup.updateName(String newName) call that helper; ensure the
helper throws BusinessException(Domain.COMMON,
OrganizationErrorCode.STUDY_GROUP_NAME_REQUIRED) when newName is null or blank
so validation behavior remains unchanged.

In @src/main/java/com/umc/product/common/dto/request/PageRequest.java:
- Around line 6-12: Remove the unnecessary inline comment inside the PageRequest
record: delete the comment "// common으로 빼도 괜찮을 거 같습니다" so the class remains
clean; locate the PageRequest record declaration (fields page and limit,
offset() method) and simply remove that commented line.

In
@src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java:
- Line 12: Remove the extra blank line appearing immediately before the
SchoolQueryUseCase interface declaration; open the file and delete the
unnecessary empty line so the package/imports (if any) or preceding content
directly precede the interface declaration for SchoolQueryUseCase.
- Line 13: Rename the interface SchoolQueryUseCase to follow the
Get{Domain}UseCase pattern: change the interface name to GetSchoolUseCase and
update all references/usages (implementations, imports, tests, DI registrations)
accordingly; ensure the filename and any constructor or factory parameters that
reference SchoolQueryUseCase are also renamed to reflect GetSchoolUseCase so
compilation and wiring remain consistent.
- Around line 3-6: The Query layer (SchoolQueryUseCase) is improperly importing
Command-layer DTOs (DeletableSchoolSummary, SchoolDeleteSearchCondition,
SchoolInfo, SchoolSummary); create equivalent DTOs inside the query package (or
move shared DTOs into a new common/shared package) and update SchoolQueryUseCase
to depend on those query/shared DTO types instead of the command ones; ensure to
add the new DTO classes (names matching the existing ones for minimal refactor)
and replace the imports/usages in SchoolQueryUseCase and any query-side callers.

In
@src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java:
- Around line 8-12: Change the findByStudyGroup signature in
LoadStudyGroupMemberPort from findByStudyGroup(StudyGroup studyGroup) to
findByStudyGroup(Long studyGroupId); update all implementations and call sites
to accept/passthrough a Long id (extract id where callers currently pass a
StudyGroup entity), and adjust any repository/adapter logic to look up members
by studyGroupId instead of receiving the StudyGroup entity (mirror the pattern
used in LoadSchoolPort).

In
@src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java:
- Line 17: The getById(Long schoolId) method in SchoolQueryUseCase should return
Optional<SchoolInfo> to avoid ambiguous nulls or undocumented exceptions; update
the interface signature from SchoolInfo getById(Long) to Optional<SchoolInfo>
getById(Long) (or alternatively document and throw a specific NotFound exception
consistently) and then update all implementations, repository/adapter methods,
and callers to handle Optional properly (use map/orElseThrow as appropriate) and
update related tests and javadoc to reflect the new contract.
- Around line 3-6: SchoolQueryUseCase currently imports command-layer DTOs
(DeletableSchoolSummary, SchoolDeleteSearchCondition, SchoolInfo, SchoolSummary)
which creates cross-layer coupling; to fix, relocate these DTOs to a
shared/common package (e.g., shared.dto or common.dto) or create equivalent
query-layer DTOs and map command DTOs to them, then update SchoolQueryUseCase
imports to reference the new package/types and adjust package declarations for
the moved DTO classes (or add mapping code in the query adapter/service that
returns the new DTOs). Ensure all references to DeletableSchoolSummary,
SchoolDeleteSearchCondition, SchoolInfo, and SchoolSummary are updated
throughout the query layer.
♻️ Duplicate comments (8)
src/main/java/com/umc/product/common/dto/request/PageResult.java (1)

5-12: limit이 0일 때 ArithmeticException 발생 위험 - 이전 리뷰 참고

이전 리뷰에서 지적된 바와 같이, totalPages() 메서드에서 limit으로 나누기 연산 시 limit이 0이면 ArithmeticException이 발생합니다. compact constructor에서 유효성 검증을 추가하는 것이 권장됩니다.

🔎 제안하는 수정
 public record PageResult<T>(
         List<T> items,
         long totalCount,
         int page,
         int limit) {
+    public PageResult {
+        if (limit <= 0) {
+            throw new IllegalArgumentException("limit must be greater than 0");
+        }
+    }
+
     public long totalPages() {
         return (totalCount + limit - 1) / limit;
     }
 }
src/main/java/com/umc/product/command/organization/domain/School.java (1)

30-36: 빌더 파라미터명 domain과 필드명 emailDomain 불일치 - 이전 리뷰 참고

이전 리뷰에서 지적된 대로, 빌더 생성자의 파라미터명 domain과 실제 필드명 emailDomain이 불일치합니다. 일관성을 위해 파라미터명을 emailDomain으로 변경하는 것을 권장합니다.

🔎 제안하는 수정
 @Builder
-private School(String name, String domain, Long logoImageId) {
-    validate(name, domain);
+private School(String name, String emailDomain, Long logoImageId) {
+    validate(name, emailDomain);
     this.name = name;
-    this.emailDomain = domain;
+    this.emailDomain = emailDomain;
     this.logoImageId = logoImageId;
 }

-private static void validate(String name, String domain) {
+private static void validate(String name, String emailDomain) {
     if (name == null || name.isBlank()) {
         throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_NAME_REQUIRED);
     }
-    if (domain == null || domain.isBlank()) {
+    if (emailDomain == null || emailDomain.isBlank()) {
         throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_DOMAIN_REQUIRED);
     }
 }
src/main/java/com/umc/product/command/organization/application/port/in/dto/response/DeletableSchoolSummary.java (1)

13-13: 불필요한 빈 줄을 제거하세요.

레코드 본문 내부에 빈 줄이 불필요합니다.

src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (1)

18-21: 유니크 제약조건 추가를 검토해주세요.

이전 리뷰에서 제안된 내용과 동일하게, ChapterSchool 엔티티에 (chapter_id, school_id) 조합에 대한 유니크 제약조건을 추가하여 데이터 중복을 방지하는 것을 권장합니다.

🔎 제안하는 수정
 @Entity
 @Getter
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"chapter_id", "school_id"}))
 public class ChapterSchool {

추가 import 필요:

import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

Based on learnings, 이전 리뷰에서도 동일한 제안이 있었습니다.

src/main/java/com/umc/product/command/organization/exception/OrganizationErrorCode.java (2)

12-32: 에러 코드 접두어 오타 수정 필요 (ORGANIZAITON → ORGANIZATION)

모든 에러 코드에서 "ORGANIZAITON"으로 오타가 있습니다. API 응답 및 로깅에 노출되므로 "ORGANIZATION"으로 수정해 주세요.


32-32: 세미콜론 중복 오류 수정 필요

Line 32에서 ;; (세미콜론 두 개)가 있어 불필요한 중복입니다. 단일 세미콜론으로 수정해 주세요.

-    STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
+    STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZATION-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");
src/main/java/com/umc/product/command/organization/domain/Gisu.java (1)

59-62: isInPeriod() 메서드 관련 확인 사항

이전 리뷰에서 언급된 대로, isInPeriod()는 시간 범위만 확인하고 isActive 플래그는 고려하지 않습니다. 의도적인 설계라면 JavaDoc을 추가하여 명확히 하거나, 필요시 isActiveAndInPeriod() 메서드를 별도로 추가하는 것을 고려해 주세요.

추가로, 조건문을 간소화할 수 있습니다:

 public boolean isInPeriod(LocalDateTime now) {
-    return (now.isEqual(startAt) || now.isAfter(startAt))
-            && now.isBefore(endAt);
+    return !now.isBefore(startAt) && now.isBefore(endAt);
 }
src/main/java/com/umc/product/command/organization/domain/StudyGroup.java (1)

93-98: 기존 리뷰에서 언급된 최적화 제안을 참고해 주세요.

대량의 멤버가 있고 일부만 변경되는 경우, 전체 clear() 후 재추가 방식은 불필요한 DELETE/INSERT를 유발할 수 있습니다. 필요시 diff 기반 업데이트를 고려해 주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60bf9f7 and 4e836f1.

📒 Files selected for processing (35)
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolSummary.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/command/organization/domain/Chapter.java
  • src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/command/organization/domain/Gisu.java
  • src/main/java/com/umc/product/command/organization/domain/School.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/command/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/common/dto/request/PageRequest.java
  • src/main/java/com/umc/product/common/dto/request/PageResult.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
🧰 Additional context used
📓 Path-based instructions (7)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/command/organization/domain/Gisu.java
  • src/main/java/com/umc/product/common/dto/request/PageRequest.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/domain/School.java
  • src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
  • src/main/java/com/umc/product/common/dto/request/PageResult.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/command/organization/domain/Chapter.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolSummary.java
**/application/port/out/**Port.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/out/**Port.java: Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle
Load Port methods must use Optional for single object returns and avoid returning Response DTOs
Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
**/*application/port/out/**Port.java

📄 CodeRabbit inference engine (CLAUDE.md)

Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/command/organization/domain/Gisu.java
  • src/main/java/com/umc/product/common/dto/request/PageRequest.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/domain/School.java
  • src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
  • src/main/java/com/umc/product/common/dto/request/PageResult.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/command/organization/domain/Chapter.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolSummary.java
**/domain/**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

State changes in Entity classes must be performed through domain methods, not via setters

Files:

  • src/main/java/com/umc/product/command/organization/domain/Gisu.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/command/organization/domain/School.java
  • src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/command/organization/domain/Chapter.java
  • src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java
**/application/port/in/**UseCase.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/in/**UseCase.java: UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern
UseCase interfaces should return Info/DTO objects, not Entity objects directly

Files:

  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
🧠 Learnings (27)
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Load Port methods must use Optional for single object returns and avoid returning Response DTOs

Applied to files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Applied to files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/GisuCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/StudyGroupMemberCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/out/persistence/**PersistenceAdapter.java : Persistence adapters (in adapter/out/persistence/) must implement Load{Domain}Port and Save{Domain}Port interfaces and delegate to JPA repositories

Applied to files:

  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadSchoolPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadChapterPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/query/application/port/out/LoadGisuPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/common/dto/request/PageRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/UpdateSchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/SchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterSchoolCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/**Info.java : Info DTOs for cross-domain responses must use {Domain}Info naming pattern and be immutable records

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/response/**Response.java : Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/UpdateSchoolInfo.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Response.java : Response DTO classes should use record type and provide static from() method for conversion from domain/info objects

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/command/organization/application/port/out/CentralOrganizationCommandPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/out/**Adapter.java : Persistence adapter classes must implement Port interfaces and delegate to Repository instances

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/command/organization/application/port/out/ChapterCommandPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/service/**/*Service.java : Service classes implementing usecases must be in application/service/{command|query}/ packages; command services use {Domain}CommandService, query services use {Domain}QueryService

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain communication from services must use Query UseCase interfaces (GetUserInfoUseCase, GetOrganizationUseCase) for accessing other domains, not direct repository access

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Service classes must not directly access other domain's Repository instances; use other domain's Query UseCase instead

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/command/organization/adapter/in/dto/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/application/port/in/SchoolCommandUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*Entity.java : Entity classes must use NoArgsConstructor(access = AccessLevel.PROTECTED), not Setter, and reference only IDs of other domains instead of direct entity references

Applied to files:

  • src/main/java/com/umc/product/command/organization/domain/School.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*domain/*Entity.java : Entity should extend BaseEntity and use JPA annotations (Entity, Table, Id, GeneratedValue, Column, Enumerated)

Applied to files:

  • src/main/java/com/umc/product/command/organization/domain/School.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*.java : State changes in Entity classes must be performed through domain methods, not via setters

Applied to files:

  • src/main/java/com/umc/product/command/organization/domain/School.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain Entity references are forbidden; only store ID fields (Column private Long {domain}Id), access other domains via UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/exception/**/*.java : Exception handling must use BusinessException, ErrorCode enums, and GlobalExceptionHandler in global/exception/ package

Applied to files:

  • src/main/java/com/umc/product/command/organization/exception/OrganizationErrorCode.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/**/*.java : Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Applied to files:

  • src/main/java/com/umc/product/command/organization/adapter/in/dto/CreateSchoolRequest.java
🧬 Code graph analysis (6)
src/main/java/com/umc/product/command/organization/domain/StudyGroup.java (6)
src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/command/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/command/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/command/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/command/organization/domain/School.java (6)
src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/command/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/command/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/command/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java (6)
src/main/java/com/umc/product/command/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/command/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/command/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/command/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java (6)
src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/command/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/command/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/command/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/command/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/command/organization/domain/Chapter.java (6)
src/main/java/com/umc/product/command/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/command/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/command/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/command/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/command/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/command/organization/domain/ChapterSchool.java (2)
src/main/java/com/umc/product/command/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/command/organization/domain/School.java (1)
  • Entity (15-47)

Comment on lines 5 to 10
public record CreateSchoolUseCaseRequest(
@NotBlank
String schoolName,
Long chapterId,
String remark
) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

명명 규칙 확인: Request vs Command 패턴

학습된 규칙에 따르면 Command DTO는 {Action}{Domain}Command 패턴을 따라야 합니다. 현재 이 클래스는 CreateSchoolUseCaseRequest로 명명되어 있습니다.

프로젝트에서 의도적으로 UseCase 요청 객체를 "Request"로 명명하고 있다면 문제없으나, 일관성을 위해 확인이 필요합니다.

Based on learnings, Command DTO naming pattern should be {Action}{Domain}Command with immutable records and validation.

만약 Command 패턴을 따른다면:

-public record CreateSchoolUseCaseRequest(
+public record CreateSchoolCommand(
         @NotBlank
         String schoolName,
         Long chapterId,
         String remark
 ) {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/request/CreateSchoolUseCaseRequest.java
around lines 5-10, The class CreateSchoolUseCaseRequest should follow the
project's Command DTO naming convention: rename the record
CreateSchoolUseCaseRequest to CreateSchoolCommand (and update the filename and
all references/usages), keep it as an immutable Java record, preserve the
validation annotations (e.g., @NotBlank on schoolName) and the same fields
(schoolName, chapterId, remark), and update any constructors/usages in
services/controllers/ports that reference CreateSchoolUseCaseRequest to use
CreateSchoolCommand.

Comment on lines 11 to 12
@NotNull
String chapterId,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

chapterId 필드의 타입과 검증 어노테이션을 검토해주세요.

chapterId 필드가 String 타입으로 선언되어 있고 @NotNull 어노테이션이 적용되어 있습니다. 다음 사항들을 확인해주세요:

  1. 타입 일관성: Chapter 엔티티의 id가 Long 타입인데, 여기서는 String으로 선언되어 있습니다. 의도한 설계인지 확인이 필요합니다.
  2. 검증 어노테이션: String 타입의 경우 @NotNull은 빈 문자열("")을 허용하므로, @NotBlank를 사용하는 것이 더 적절합니다.
🔎 타입을 Long으로 수정하는 경우
-        @NotNull
-        String chapterId,
+        @NotNull
+        Long chapterId,
🔎 String 타입을 유지하는 경우
-        @NotNull
+        @NotBlank
         String chapterId,
📝 Committable suggestion

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

Suggested change
@NotNull
String chapterId,
@NotNull
Long chapterId,
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/request/UpdateSchoolUseCaseRequest.java
around lines 11-12, The chapterId field in UpdateSchoolUseCaseRequest is
declared as String with @NotNull but the Chapter entity uses a Long id; either
change the chapterId field type to Long and keep @NotNull (update
constructor/getter/setter and any mapping/code using UpdateSchoolUseCaseRequest
accordingly), or if you intend to keep String, replace @NotNull with @NotBlank
to reject empty strings and import javax.validation.constraints.NotBlank; ensure
all code paths that construct or consume UpdateSchoolUseCaseRequest are updated
to use the chosen type and validation annotation.

Comment on lines 1 to 7
package com.umc.product.command.organization.application.port.in.dto.response;

public record SchoolDeleteSearchCondition(
String keyword,
Long chapterId
) {
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

패키지 위치가 잘못되었습니다.

SchoolDeleteSearchCondition은 검색 조건(입력 파라미터)인데 dto.response 패키지에 위치해 있습니다. 이 클래스는 SchoolQueryUseCase.getList() 메서드의 파라미터로 사용되므로 응답(response)이 아닌 요청(request) 또는 조건(condition) 타입입니다.

🔎 올바른 패키지 위치

검색 조건은 다음 위치 중 하나로 이동해야 합니다:

옵션 1: request 패키지 (권장)

src/main/java/com/umc/product/command/organization/application/port/in/dto/request/SchoolDeleteSearchCondition.java

옵션 2: 별도의 condition 패키지

src/main/java/com/umc/product/command/organization/application/port/in/dto/condition/SchoolDeleteSearchCondition.java

또한 File 1의 SchoolSummary와 마찬가지로, 조회 유스케이스에서 사용되므로 query 패키지 하위로 이동하는 것도 고려해주세요.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
around lines 1-7, The SchoolDeleteSearchCondition record is in the dto.response
package but is an input/search parameter for SchoolQueryUseCase.getList(); move
this type to an input/request or condition package (recommended:
application.port.in.dto.request or application.port.in.dto.condition) and update
its package declaration accordingly so callers like SchoolQueryUseCase.getList()
import the type from the new package; ensure any references/imports to
SchoolDeleteSearchCondition are updated across the codebase.

Comment on lines +5 to +13
public record SchoolInfo(
Long chapterId,
String chapterName,
String schoolName,
Long schoolId,
String remark,
LocalDate createdAt,
LocalDate updatedAt
) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Info DTO 구조는 적절하나, 날짜 타입 검토 필요

record 타입과 {Domain}Info 네이밍 패턴이 학습된 가이드라인에 맞게 잘 적용되어 있습니다.

그러나 createdAtupdatedAt 필드가 LocalDate로 선언되어 있어 시간 정보가 손실될 수 있습니다. 일반적으로 감사(audit) 필드는 시간 정보까지 포함하는 LocalDateTime을 사용합니다. 의도적으로 날짜만 필요한 경우가 아니라면 LocalDateTime으로 변경을 권장합니다.

🔎 제안하는 수정
-import java.time.LocalDate;
+import java.time.LocalDateTime;

 public record SchoolInfo(
         Long chapterId,
         String chapterName,
         String schoolName,
         Long schoolId,
         String remark,
-        LocalDate createdAt,
-        LocalDate updatedAt
+        LocalDateTime createdAt,
+        LocalDateTime updatedAt
 ) {
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolInfo.java
around lines 5-13, The SchoolInfo record declares createdAt and updatedAt as
LocalDate which loses time precision; update the SchoolInfo record to use
java.time.LocalDateTime for those two fields (createdAt and updatedAt) and
adjust imports, any factory/mapping code that constructs SchoolInfo (e.g.,
mappers, constructors, or service methods that populate SchoolInfo) to pass
LocalDateTime values instead of LocalDate; ensure any
serialization/deserialization or DTO conversions that expect date-only values
are updated to handle LocalDateTime consistently.

Comment on lines 1 to 7
package com.umc.product.command.organization.application.port.in.dto.response;

public record SchoolSummary(
Long schoolId,
String schoolName
) {
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

패키지 위치를 재검토해주세요.

이 DTO는 SchoolQueryUseCase.getAll()의 반환 타입으로 사용되는 조회용 응답 DTO인데, command.organization.application.port.in.dto.response 패키지에 위치해 있습니다.

조회(Query) 유스케이스에서 사용되는 응답 DTO는 query 패키지 하위에 위치하는 것이 CQRS 패턴과 헥사고날 아키텍처에 더 부합합니다.

🔎 권장 패키지 위치

다음 위치로 이동을 고려해주세요:

src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolSummary.java

또는 command와 query 모두에서 사용된다면 공통 DTO 패키지를 고려할 수 있습니다:

src/main/java/com/umc/product/common/dto/response/SchoolSummary.java
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/application/port/in/dto/response/SchoolSummary.java
around lines 1-7, The SchoolSummary record is placed under the command package
but is used as the response type for the query use case
(SchoolQueryUseCase.getAll()); move the SchoolSummary record to a
query-appropriate package (e.g.,
query.organization.application.port.in.dto.response) or a shared common DTO
package if used by both sides, update its package declaration accordingly, and
adjust any imports/usages (including SchoolQueryUseCase.getAll() return type
references and other callers) to the new package so compilation and imports
remain consistent.

import java.util.List;


public interface SchoolQueryUseCase {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

UseCase 네이밍 패턴을 준수하세요.

코딩 가이드라인에 따르면 Query UseCase는 Get{Domain}UseCase 패턴을 따라야 합니다. 현재 SchoolQueryUseCase는 이 패턴을 따르지 않습니다.

🔎 제안하는 수정
-public interface SchoolQueryUseCase  {
+public interface GetSchoolUseCase {

Based on learnings, 코딩 가이드라인 기준입니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/application/port/in/SchoolQueryUseCase.java
around line 13, Rename the interface SchoolQueryUseCase to follow the
Get{Domain}UseCase pattern: change the interface name to GetSchoolUseCase and
update all references/usages (implementations, imports, tests, DI registrations)
accordingly; ensure the filename and any constructor or factory parameters that
reference SchoolQueryUseCase are also renamed to reflect GetSchoolUseCase so
compilation and wiring remain consistent.

Comment on lines 8 to 12
public interface LoadStudyGroupMemberPort {

Optional<StudyGroupMember> findById(Long id);

List<StudyGroupMember> findByStudyGroup(StudyGroup studyGroup);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

findByStudyGroup 메서드 파라미터 타입 개선 필요

findByIdOptional을 반환하는 점은 가이드라인에 부합합니다.

그러나 LoadSchoolPort와 동일하게 findByStudyGroup(StudyGroup studyGroup) 메서드가 엔티티를 직접 참조하고 있습니다. 도메인 간 결합도를 낮추기 위해 Long studyGroupId를 파라미터로 사용하는 것이 권장됩니다. Based on learnings, 다른 도메인의 직접 엔티티 참조 대신 ID를 사용해야 합니다.

🔎 제안하는 수정
 public interface LoadStudyGroupMemberPort {

     Optional<StudyGroupMember> findById(Long id);

-    List<StudyGroupMember> findByStudyGroup(StudyGroup studyGroup);
+    List<StudyGroupMember> findByStudyGroupId(Long studyGroupId);
 }
📝 Committable suggestion

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

Suggested change
public interface LoadStudyGroupMemberPort {
Optional<StudyGroupMember> findById(Long id);
List<StudyGroupMember> findByStudyGroup(StudyGroup studyGroup);
public interface LoadStudyGroupMemberPort {
Optional<StudyGroupMember> findById(Long id);
List<StudyGroupMember> findByStudyGroupId(Long studyGroupId);
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/application/port/out/LoadStudyGroupMemberPort.java
around lines 8-12, Change the findByStudyGroup signature in
LoadStudyGroupMemberPort from findByStudyGroup(StudyGroup studyGroup) to
findByStudyGroup(Long studyGroupId); update all implementations and call sites
to accept/passthrough a Long id (extract id where callers currently pass a
StudyGroup entity), and adjust any repository/adapter logic to look up members
by studyGroupId instead of receiving the StudyGroup entity (mirror the pattern
used in LoadSchoolPort).


List<SchoolSummary> getAll(Long gisuId);

SchoolInfo getById(Long schoolId);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

getById 메서드의 반환 타입을 Optional로 고려해보세요.

getById(Long schoolId) 메서드가 SchoolInfo를 직접 반환하고 있습니다.

고려사항:

  • 존재하지 않는 schoolId가 전달될 경우 어떻게 처리되나요?
  • 예외를 던지는 방식인가요, 아니면 null을 반환하나요?

제안:
더 명시적인 에러 처리를 위해 Optional<SchoolInfo>를 반환하거나, 존재하지 않을 경우 명확한 예외를 던지는 것을 문서화하는 것을 권장합니다.

🔎 Optional을 사용하는 경우
-    SchoolInfo getById(Long schoolId);
+    Optional<SchoolInfo> getById(Long schoolId);
📝 Committable suggestion

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

Suggested change
SchoolInfo getById(Long schoolId);
Optional<SchoolInfo> getById(Long schoolId);
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java
around line 17, The getById(Long schoolId) method in SchoolQueryUseCase should
return Optional<SchoolInfo> to avoid ambiguous nulls or undocumented exceptions;
update the interface signature from SchoolInfo getById(Long) to
Optional<SchoolInfo> getById(Long) (or alternatively document and throw a
specific NotFound exception consistently) and then update all implementations,
repository/adapter methods, and callers to handle Optional properly (use
map/orElseThrow as appropriate) and update related tests and javadoc to reflect
the new contract.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Fix all issues with AI Agents 🤖
In
@src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java:
- Around line 6-9: CreateSchoolRequest is missing validation on chapterId and
possibly remark: add @NotNull to the chapterId record component to enforce that
a School must belong to a Chapter, and if remark should be length-limited add an
appropriate annotation such as @Size(max=...) or @Length(...) to the remark
component; update imports for javax.validation.constraints.NotNull and the
chosen size annotation and run validation tests to confirm behavior.

In
@src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java:
- Around line 7-11: Update the UpdateSchoolRequest record so the chapterId
parameter uses @NotBlank instead of @NotNull to reject null, empty, or
whitespace-only strings; update imports if needed and ensure the annotation is
applied to the chapterId component in the UpdateSchoolRequest declaration to
enforce correct DTO validation.

In
@src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java:
- Line 8: The interface name is mistyped as ChapterPortQueryPort; rename the
interface to ChapterQueryPort and update all references (imports,
implementations, usages) accordingly — e.g., change the declaration public
interface ChapterPortQueryPort to public interface ChapterQueryPort and refactor
any classes, tests, or files that implement or reference ChapterPortQueryPort to
use ChapterQueryPort instead to avoid compilation errors.

In
@src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java:
- Line 9: The interface ChapterSchoolQueryPort violates the naming convention
for Port Out; rename it to LoadChapterSchoolPort (or if it exposes save methods,
split into LoadChapterSchoolPort and SaveChapterSchoolPort) and update all
references (implementations, injection points, constructor params, config beans,
and tests) to use the new name; ensure method signatures remain unchanged and
adjust any import statements and documentation/comments to reflect the new
interface name.

In
@src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java:
- Around line 7-12: Rename the port interface GisuQueryPort to LoadGisuPort to
follow the Load{Domain}Port naming convention; keep the method signatures
findById(Optional<Gisu>) and findAll() unchanged, update all implementing
classes, DTOs, tests and DI/bean references that import or reference
GisuQueryPort to use LoadGisuPort, and ensure package-level consistency with
other query ports so compilation and wiring remain correct.

In
@src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java:
- Around line 12-14: The methods in SchoolQueryPort (findAll and findByChapter)
return unbounded Lists which can cause OOM and performance issues; update the
interface to support pagination by replacing or overloading these signatures to
accept a Pageable (or your project's custom pagination DTO) and return a
Page<School> (or paginated DTO), e.g., add/modify methods like findAll(Pageable
pageable) and findByChapter(Chapter chapter, Pageable pageable), then propagate
changes to implementations and callers to pass Pageable and handle Page results.
- Around line 3-4: SchoolQueryPort currently imports and exposes Command-side
domain entities Chapter and School, coupling Query to Command; verify whether
this is intentional, and if not create Query/read models (e.g., SchoolDto,
ChapterDto) and change SchoolQueryPort to return those DTOs instead of
com.umc.product.command.organization.domain.School/Chapter, then implement
mapping in the adapter/mapper layer (map from domain School/Chapter to
SchoolDto/ChapterDto) and update any callers; if sharing the same domain is
intentional, add a clear comment/docs on SchoolQueryPort explaining the
deliberate coupling and the maintenance risk so future changes are explicit.

In
@src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java:
- Around line 8-13: Rename the read-only port interface
StudyGroupMemberQueryPort to follow the Load{Domain}Port convention: change the
interface name to LoadStudyGroupMemberPort and keep the same method signatures
(findById and findByStudyGroup). Update all references/imports/usages and any
implementing classes to implement LoadStudyGroupMemberPort instead of
StudyGroupMemberQueryPort, and adjust package-level declarations if necessary to
remain in application.port.out; ensure compilation by updating method references
where the old interface type was used.

In
@src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java:
- Around line 6-11: The StudyGroupQueryPort interface lacks JavaDoc and
null-safety annotations; add class-level Javadoc describing the port’s
responsibility and method-level Javadoc for findById(Long id) and
findByName(String name), then annotate parameters with a nullability annotation
(e.g., @NonNull or @NotNull) and import the chosen annotation (from lombok or
javax/jetbrains) so callers and tools can enforce non-null inputs.
- Line 3: The query port currently imports and returns the command-side domain
entity StudyGroup; to separate query/read model concerns create a dedicated read
DTO or class (e.g., StudyGroupReadModel) and update StudyGroupQueryPort to use
that type instead of StudyGroup; modify the query-side adapter/repository to map
from the domain StudyGroup (or persistence entity) to StudyGroupReadModel
(implement mapping in methods used by StudyGroupQueryPort), and update all
callers of StudyGroupQueryPort to consume the new read model; keep StudyGroup in
the command side unchanged and only introduce the read model and mapping in
query-side code (interfaces: StudyGroupQueryPort, adapter/repository
implementations, mapping helpers, and any service methods that call the port).

In
@src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java:
- Line 1: The SchoolQueryUseCase interface is in the wrong package; move its
declaration into the application port "in" package to follow hexagonal
conventions: change the package declaration to
com.umc.product.query.organization.application.port.in and relocate the file to
the corresponding folder (e.g.,
src/main/java/.../application/port/in/SchoolQueryUseCase.java), ensuring any
imports or references to SchoolQueryUseCase are updated to the new package.
♻️ Duplicate comments (6)
src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java (1)

14-14: 이전 리뷰 내용 미반영 확인

이전 리뷰에서 findAllByGisu(Gisu gisu) 메서드의 파라미터를 Long gisuId로 변경하여 결합도를 낮추는 제안이 있었습니다. 현재 코드에 아직 반영되지 않았습니다.

도메인 엔티티를 직접 파라미터로 받는 것도 동일 aggregate 내에서는 문제없지만, 팀 컨벤션에 따라 ID만 전달하는 방식도 고려해보세요.

src/main/java/com/umc/product/query/organization/application/port/in/dto/response/DeletableSchoolSummary.java (1)

12-14: 불필요한 빈 줄을 제거하세요.

record 본문 내부에 빈 줄(line 13)이 불필요합니다. 다른 record들과의 일관성을 위해 제거해주세요.

🔎 제안하는 수정
 public record DeletableSchoolSummary(
         Long schoolId,
         String schoolName,
         Long chapterId,
         String chapterName,
         LocalDate createdAt,
         boolean isActive
 ) {
-
 }
src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolInfo.java (1)

5-14: 감사(audit) 필드에 LocalDateTime 사용을 권장합니다.

createdAtupdatedAt 필드가 LocalDate로 선언되어 시간 정보가 손실됩니다. 감사 필드는 일반적으로 시간 정보까지 포함하는 LocalDateTime을 사용합니다. 의도적으로 날짜만 필요한 경우가 아니라면 LocalDateTime으로 변경을 권장합니다.

🔎 제안하는 수정
-import java.time.LocalDate;
+import java.time.LocalDateTime;

 public record SchoolInfo(
         Long chapterId,
         String chapterName,
         String schoolName,
         Long schoolId,
         String remark,
-        LocalDate createdAt,
-        LocalDate updatedAt
+        LocalDateTime createdAt,
+        LocalDateTime updatedAt
 ) {
 }
src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java (1)

5-13: 감사 필드 타입 개선 및 사용 여부 검증이 필요합니다.

  1. createdAtupdatedAt 필드가 LocalDate로 선언되어 시간 정보가 손실됩니다. LocalDateTime 사용을 권장합니다.

  2. 이전 리뷰에서 이 DTO가 실제로 사용되지 않는다고 지적되었습니다. 현재 코드베이스에서 실제 사용처를 확인하여, 불필요하다면 제거하고 필요하다면 적절히 활용되고 있는지 검증해주세요.

🔎 제안하는 수정 (타입 변경)
-import java.time.LocalDate;
+import java.time.LocalDateTime;

 public record UpdateSchoolInfo(
         String newSchoolName,
         Long chapterId,
         String chapterName,
         String remark,
-        LocalDate createdAt,
-        LocalDate updatedAt
+        LocalDateTime createdAt,
+        LocalDateTime updatedAt
 ) {
 }

다음 스크립트로 사용 여부를 확인해주세요:

#!/bin/bash
# UpdateSchoolInfo의 실제 사용처를 확인합니다.

echo "=== UpdateSchoolInfo 참조 검색 ==="
rg -n "UpdateSchoolInfo" --type java -C 3

echo -e "\n=== UpdateSchoolInfo를 반환하는 메서드 검색 ==="
rg -n "UpdateSchoolInfo.*\(" --type java -C 2

echo -e "\n=== UpdateSchoolInfo를 생성하는 코드 검색 ==="
rg -n "new UpdateSchoolInfo|UpdateSchoolInfo\(" --type java -C 2
src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java (2)

3-6: Query 레이어가 Command 레이어의 DTO에 의존하고 있습니다.

Query 레이어(query.organization.application.port)가 Command 레이어(command.organization.application.port.in.dto.response)의 DTO를 직접 import하고 있습니다. AI 요약에 따르면 Query 레이어의 동일한 DTO들이 이미 존재합니다:

  • query.organization.application.port.in.dto.response.SchoolSummary
  • query.organization.application.port.in.dto.response.SchoolInfo
  • query.organization.application.port.in.dto.response.DeletableSchoolSummary
  • query.organization.application.port.in.dto.response.SchoolDeleteSearchCondition
🔎 권장하는 수정
-import com.umc.product.command.organization.application.port.in.dto.response.DeletableSchoolSummary;
-import com.umc.product.command.organization.application.port.in.dto.response.SchoolDeleteSearchCondition;
-import com.umc.product.command.organization.application.port.in.dto.response.SchoolInfo;
-import com.umc.product.command.organization.application.port.in.dto.response.SchoolSummary;
+import com.umc.product.query.organization.application.port.in.dto.response.DeletableSchoolSummary;
+import com.umc.product.query.organization.application.port.in.dto.response.SchoolDeleteSearchCondition;
+import com.umc.product.query.organization.application.port.in.dto.response.SchoolInfo;
+import com.umc.product.query.organization.application.port.in.dto.response.SchoolSummary;

코딩 가이드라인에서 헥사고날 아키텍처 준수를 요구합니다.


11-11: 불필요한 빈 줄을 제거하세요.

인터페이스 선언 전에 불필요한 빈 줄이 있습니다.

🔎 제안하는 수정
 import java.util.List;
-

 public interface SchoolQueryUseCase {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e836f1 and 668b8ca.

📒 Files selected for processing (14)
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolSummary.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
🧰 Additional context used
📓 Path-based instructions (4)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolSummary.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolSummary.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
**/application/port/out/**Port.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/application/port/out/**Port.java: Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle
Load Port methods must use Optional for single object returns and avoid returning Response DTOs
Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Files:

  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
**/*application/port/out/**Port.java

📄 CodeRabbit inference engine (CLAUDE.md)

Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Files:

  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
🧠 Learnings (20)
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Load Port methods must use Optional for single object returns and avoid returning Response DTOs

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/**Info.java : Info DTOs for cross-domain responses must use {Domain}Info naming pattern and be immutable records

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/response/**Response.java : Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Response.java : Response DTO classes should use record type and provide static from() method for conversion from domain/info objects

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/in/dto/response/UpdateSchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain communication from services must use Query UseCase interfaces (GetUserInfoUseCase, GetOrganizationUseCase) for accessing other domains, not direct repository access

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/service/**/*Service.java : Service classes implementing usecases must be in application/service/{command|query}/ packages; command services use {Domain}CommandService, query services use {Domain}QueryService

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/**/*.java : Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Service classes must not directly access other domain's Repository instances; use other domain's Query UseCase instead

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java
  • src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
🔇 Additional comments (9)
src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java (1)

10-15: 변환 메서드 구현이 적절합니다

toUseCaseRequest() 메서드가 올바르게 구현되어 어댑터 계층의 DTO를 유스케이스 계층의 DTO로 깔끔하게 변환하고 있습니다. 헥사고날 아키텍처의 계층 분리 원칙을 잘 따르고 있습니다.

src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java (1)

12-18: LGTM!

변환 메서드가 올바르게 구현되어 있습니다. 모든 필드가 적절히 매핑되고 있으며, DTO 계층에서 비즈니스 로직을 포함하지 않는 깔끔한 구조입니다.

Based on learnings, Request DTOs should provide toCommand() or toUseCaseRequest() method to convert to UseCase command objects.

src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java (1)

3-4: 해당 패턴은 프로젝트 전체에 일관되게 적용된 아키텍처 설계입니다. Query 포트의 모든 파일에서 Command 측의 도메인 엔티티를 참조하고 있으며, 이는 Shared Kernel 패턴을 의도적으로 적용한 것으로 확인됩니다. 추가 조치 필요 없습니다.

src/main/java/com/umc/product/query/organization/application/port/out/SchoolQueryPort.java (1)

10-10: LGTM!

Optional을 사용한 단일 객체 반환, 기술 중립적 메서드명, 도메인 엔티티 반환 등 모든 가이드라인을 준수하고 있습니다.

Based on learnings, Load Port 메서드가 올바르게 구현되었습니다.

src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java (1)

6-11: Port Out 인터페이스 설계가 헥사고날 아키텍처 원칙을 잘 준수하고 있습니다.

인터페이스가 다음 원칙들을 올바르게 따르고 있습니다:

  • Query 전용 포트로 읽기 작업만 수행 (인터페이스 분리 원칙)
  • Optional<도메인엔티티> 반환 (Response DTO 반환 금지 원칙)
  • 기술 중립적 메서드명 사용 (findById, findByName)

Based on learnings, Port Out 인터페이스 설계 가이드라인을 모두 충족합니다.

src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java (1)

11-15: 파라미터 설계는 프로젝트 패턴에 부합함

다음 부분들은 잘 구현되었습니다:

  • Optional 사용으로 단일 객체 반환 처리 ✓
  • 기술 중립적인 메서드명 사용 (findById, findByChapterAndSchool, findAllByChapter) ✓
  • 도메인 엔티티 직접 반환 (Response DTO 사용 안 함) ✓
  • 도메인 엔티티를 파라미터로 수용하는 설계는 프로젝트 전체에 걸친 Query Port의 확립된 패턴 ✓

ChapterPortQueryPort, SchoolQueryPort, StudyGroupMemberQueryPort 등 다른 Query Port 인터페이스들도 동일하게 관련 도메인 엔티티를 파라미터로 받으며, 이는 CQRS 패턴에서 의도된 설계입니다.

src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolSummary.java (1)

3-7: LGTM! DTO 구조가 적절합니다.

간단한 요약 정보를 담는 record 타입으로 적절하게 구현되었습니다. 불변성과 네이밍 컨벤션이 잘 지켜지고 있습니다.

src/main/java/com/umc/product/query/organization/application/port/in/dto/response/SchoolDeleteSearchCondition.java (1)

3-7: LGTM! 검색 조건 DTO 구조가 적절합니다.

페이지네이션 쿼리를 위한 검색 조건을 담는 record 타입으로 적절하게 구현되었습니다.

src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java (1)

4-4: 검색 조건 DTO가 response 패키지에 위치하고 있습니다.

SchoolDeleteSearchCondition은 검색 조건으로 사용되는 입력 파라미터인데, dto.response 패키지에서 import되고 있습니다. 검색 조건은 일반적으로 request 또는 별도의 condition 패키지에 위치해야 합니다.

💡 권장 사항

SchoolDeleteSearchCondition을 다음 중 한 곳으로 이동하는 것을 고려하세요:

  • query.organization.application.port.in.dto.request.SchoolDeleteSearchCondition
  • query.organization.application.port.in.dto.condition.SchoolDeleteSearchCondition

코딩 가이드라인의 구조 및 설계 원칙에 따라 DTO의 목적에 맞는 패키지 배치가 필요합니다.

⛔ Skipped due to learnings
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/**/*.java : Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Comment on lines 6 to 9
public record CreateSchoolRequest(
@NotBlank String schoolName,
Long chapterId,
String remark) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

chapterId 필드에 대한 유효성 검증 누락

chapterId 필드에 유효성 검증 어노테이션이 없습니다. School은 Chapter에 소속되어야 하는 도메인 특성상 chapterId는 필수값으로 보입니다. null 값이 허용되어야 하는지 확인하고, 필수값이라면 @NotNull 어노테이션을 추가해야 합니다.

또한 remark 필드도 선택적 필드라면 문제없지만, 길이 제한이 필요한 경우 @Size 또는 @Length 어노테이션 추가를 고려하세요.

🔎 chapterId에 @NotNull 추가 제안
 public record CreateSchoolRequest(
         @NotBlank String schoolName,
-        Long chapterId,
+        @NotNull Long chapterId,
         String remark) {

Based on learnings, Request DTOs는 적절한 validation 어노테이션을 포함해야 합니다.

📝 Committable suggestion

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

Suggested change
public record CreateSchoolRequest(
@NotBlank String schoolName,
Long chapterId,
String remark) {
public record CreateSchoolRequest(
@NotBlank String schoolName,
@NotNull Long chapterId,
String remark) {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/adapter/in/dto/request/CreateSchoolRequest.java
around lines 6-9, CreateSchoolRequest is missing validation on chapterId and
possibly remark: add @NotNull to the chapterId record component to enforce that
a School must belong to a Chapter, and if remark should be length-limited add an
appropriate annotation such as @Size(max=...) or @Length(...) to the remark
component; update imports for javax.validation.constraints.NotNull and the
chosen size annotation and run validation tests to confirm behavior.

Comment on lines 7 to 11
public record UpdateSchoolRequest(
@NotNull Long schoolId,
@NotBlank String schoolName,
@NotNull String chapterId,
String remark) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

chapterId 필드에 @notblank 사용 권장

chapterId가 String 타입이므로 @NotNull 대신 @NotBlank를 사용해야 합니다. @NotNull은 빈 문자열("")을 허용하지만, @NotBlank는 null, 빈 문자열, 공백만 있는 문자열을 모두 검증합니다.

🔎 제안하는 수정 사항
 public record UpdateSchoolRequest(
         @NotNull Long schoolId,
         @NotBlank String schoolName,
-        @NotNull String chapterId,
+        @NotBlank String chapterId,
         String remark) {

Based on learnings, Request DTOs must include appropriate validation annotations.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/command/organization/adapter/in/dto/request/UpdateSchoolRequest.java
around lines 7-11, Update the UpdateSchoolRequest record so the chapterId
parameter uses @NotBlank instead of @NotNull to reject null, empty, or
whitespace-only strings; update imports if needed and ensure the annotation is
applied to the chapterId component in the UpdateSchoolRequest declaration to
enforce correct DTO validation.

import java.util.List;
import java.util.Optional;

public interface ChapterPortQueryPort {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

인터페이스명 오타 수정 필요

인터페이스명이 ChapterPortQueryPort로 되어 있어 "Port"가 중복됩니다. ChapterQueryPort로 수정해야 합니다.

🔎 수정 제안
-public interface ChapterPortQueryPort {
+public interface ChapterQueryPort {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/out/ChapterPortQueryPort.java
around line 8, The interface name is mistyped as ChapterPortQueryPort; rename
the interface to ChapterQueryPort and update all references (imports,
implementations, usages) accordingly — e.g., change the declaration public
interface ChapterPortQueryPort to public interface ChapterQueryPort and refactor
any classes, tests, or files that implement or reference ChapterPortQueryPort to
use ChapterQueryPort instead to avoid compilation errors.

import java.util.List;
import java.util.Optional;

public interface ChapterSchoolQueryPort {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

인터페이스 네이밍 규칙 불일치

코딩 가이드라인에 따르면, Port Out 인터페이스는 Load{Domain}Port 또는 Save{Domain}Port 패턴을 따라야 합니다. 현재 ChapterSchoolQueryPort는 이 규칙에 맞지 않습니다.

Based on learnings, Port Out interface 네이밍은 ISP(Interface Segregation Principle)를 따라 Load와 Save로 분리되어야 합니다.

🔎 제안하는 수정사항
-public interface ChapterSchoolQueryPort {
+public interface LoadChapterSchoolPort {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/out/ChapterSchoolQueryPort.java
around line 9, The interface ChapterSchoolQueryPort violates the naming
convention for Port Out; rename it to LoadChapterSchoolPort (or if it exposes
save methods, split into LoadChapterSchoolPort and SaveChapterSchoolPort) and
update all references (implementations, injection points, constructor params,
config beans, and tests) to use the new name; ensure method signatures remain
unchanged and adjust any import statements and documentation/comments to reflect
the new interface name.

Comment on lines 7 to 12
public interface GisuQueryPort {

Optional<Gisu> findById(Long id);

List<Gisu> findAll();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

선택적 개선: 네이밍 컨벤션 일관성 고려

현재 인터페이스명이 GisuQueryPort로 되어 있습니다. CQRS 패턴에서는 문제없지만, 팀의 코딩 가이드라인에 따르면 Port Out 인터페이스는 Load{Domain}Port, Save{Domain}Port 패턴을 따르도록 권장됩니다.

조회 전용 포트이므로 LoadGisuPort로 변경하는 것을 고려해보세요. 다만 현재 구조도 Query 패키지 내에서는 충분히 명확하므로, 팀 내 다른 Query Port들과 일관성을 유지하는 방향으로 결정하시면 됩니다.

메서드 구현 자체는 모두 올바릅니다:

  • findByIdOptional<Gisu> 반환 ✓
  • findAll는 도메인 엔티티 리스트 반환 ✓
  • 메서드명이 기술 중립적 ✓

Based on learnings, Port Out 인터페이스는 ISP 원칙에 따라 Load/Save로 분리하고 Load{Domain}Port 네이밍을 권장합니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/out/GisuQueryPort.java
around lines 7-12, Rename the port interface GisuQueryPort to LoadGisuPort to
follow the Load{Domain}Port naming convention; keep the method signatures
findById(Optional<Gisu>) and findAll() unchanged, update all implementing
classes, DTOs, tests and DI/bean references that import or reference
GisuQueryPort to use LoadGisuPort, and ensure package-level consistency with
other query ports so compilation and wiring remain correct.

Comment on lines 8 to 13
public interface StudyGroupMemberQueryPort {

Optional<StudyGroupMember> findById(Long id);

List<StudyGroupMember> findByStudyGroup(StudyGroup studyGroup);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인터페이스 네이밍 컨벤션 위반

코딩 가이드라인에 따르면 Port Out 인터페이스는 Load{Domain}Port 또는 Save{Domain}Port 네이밍 패턴을 따라야 합니다. 현재 StudyGroupMemberQueryPort는 읽기 전용 작업만 수행하므로 LoadStudyGroupMemberPort로 변경하는 것이 좋습니다.

🔎 제안하는 네이밍 변경
-public interface StudyGroupMemberQueryPort {
+public interface LoadStudyGroupMemberPort {

Based on coding guidelines: Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupMemberQueryPort.java
around lines 8-13, Rename the read-only port interface StudyGroupMemberQueryPort
to follow the Load{Domain}Port convention: change the interface name to
LoadStudyGroupMemberPort and keep the same method signatures (findById and
findByStudyGroup). Update all references/imports/usages and any implementing
classes to implement LoadStudyGroupMemberPort instead of
StudyGroupMemberQueryPort, and adjust package-level declarations if necessary to
remain in application.port.out; ensure compilation by updating method references
where the old interface type was used.

@@ -0,0 +1,11 @@
package com.umc.product.query.organization.application.port.out;

import com.umc.product.command.organization.domain.StudyGroup;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

CQRS 패턴에서 query side 전용 read model 분리를 고려해볼 수 있습니다.

현재 query side에서 command side의 도메인 엔티티(StudyGroup)를 직접 사용하고 있습니다. 이는 많은 CQRS 구현에서 허용되는 방식이지만, 더 엄격한 분리가 필요한 경우 다음을 고려할 수 있습니다:

  • Query 전용 read model (예: StudyGroupReadModel) 생성
  • Command와 Query의 데이터 요구사항이 다를 때 유용
  • 현재 단계에서는 불필요할 수 있으므로 향후 복잡도 증가 시 고려

프로젝트의 CQRS 적용 수준과 복잡도에 따라 선택적으로 적용하시면 됩니다.

Also applies to: 8-8, 10-10

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
around line 3, The query port currently imports and returns the command-side
domain entity StudyGroup; to separate query/read model concerns create a
dedicated read DTO or class (e.g., StudyGroupReadModel) and update
StudyGroupQueryPort to use that type instead of StudyGroup; modify the
query-side adapter/repository to map from the domain StudyGroup (or persistence
entity) to StudyGroupReadModel (implement mapping in methods used by
StudyGroupQueryPort), and update all callers of StudyGroupQueryPort to consume
the new read model; keep StudyGroup in the command side unchanged and only
introduce the read model and mapping in query-side code (interfaces:
StudyGroupQueryPort, adapter/repository implementations, mapping helpers, and
any service methods that call the port).

Comment on lines 6 to 11
public interface StudyGroupQueryPort {

Optional<StudyGroup> findById(Long id);

Optional<StudyGroup> findByName(String name);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

JavaDoc과 파라미터 검증 어노테이션을 추가하면 더 견고한 인터페이스가 됩니다.

다음 개선사항을 고려해주세요:

  • 인터페이스와 각 메서드에 JavaDoc 추가하여 용도와 동작 명시
  • 파라미터에 @NonNull 또는 @NotNull 어노테이션 추가하여 null 안전성 보장

As per coding guidelines, 입력 검증과 코드 가독성 향상을 위해 권장됩니다.

🔎 개선 제안
 public interface StudyGroupQueryPort {
+
+    /**
+     * ID로 스터디 그룹을 조회합니다.
+     *
+     * @param id 스터디 그룹 ID
+     * @return 조회된 스터디 그룹 (Optional)
+     */
+    Optional<StudyGroup> findById(@NonNull Long id);
-    Optional<StudyGroup> findById(Long id);
 
+    /**
+     * 이름으로 스터디 그룹을 조회합니다.
+     *
+     * @param name 스터디 그룹 이름
+     * @return 조회된 스터디 그룹 (Optional)
+     */
+    Optional<StudyGroup> findByName(@NonNull String name);
-    Optional<StudyGroup> findByName(String name);
 }
📝 Committable suggestion

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

Suggested change
public interface StudyGroupQueryPort {
Optional<StudyGroup> findById(Long id);
Optional<StudyGroup> findByName(String name);
}
public interface StudyGroupQueryPort {
/**
* ID로 스터디 그룹을 조회합니다.
*
* @param id 스터디 그룹 ID
* @return 조회된 스터디 그룹 (Optional)
*/
Optional<StudyGroup> findById(@NonNull Long id);
/**
* 이름으로 스터디 그룹을 조회합니다.
*
* @param name 스터디 그룹 이름
* @return 조회된 스터디 그룹 (Optional)
*/
Optional<StudyGroup> findByName(@NonNull String name);
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/out/StudyGroupQueryPort.java
around lines 6-11, The StudyGroupQueryPort interface lacks JavaDoc and
null-safety annotations; add class-level Javadoc describing the port’s
responsibility and method-level Javadoc for findById(Long id) and
findByName(String name), then annotate parameters with a nullability annotation
(e.g., @NonNull or @NotNull) and import the chosen annotation (from lombok or
javax/jetbrains) so callers and tools can enforce non-null inputs.

@@ -0,0 +1,20 @@
package com.umc.product.query.organization.application.port;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

패키지 구조가 헥사고날 아키텍처 규칙을 위반합니다.

Query UseCase 인터페이스는 application/port/in 패키지에 위치해야 하는데, 현재 /in이 누락되어 있습니다.

🔎 권장하는 수정
-package com.umc.product.query.organization.application.port;
+package com.umc.product.query.organization.application.port.in;

파일도 다음 경로로 이동해야 합니다:
src/main/java/com/umc/product/query/organization/application/port/in/SchoolQueryUseCase.java

Based on learnings, UseCase interfaces must be defined in application/port/in/ packages.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/query/organization/application/port/SchoolQueryUseCase.java
around line 1, The SchoolQueryUseCase interface is in the wrong package; move
its declaration into the application port "in" package to follow hexagonal
conventions: change the package declaration to
com.umc.product.query.organization.application.port.in and relocate the file to
the corresponding folder (e.g.,
src/main/java/.../application/port/in/SchoolQueryUseCase.java), ensuring any
imports or references to SchoolQueryUseCase are updated to the new package.

Comment on lines 12 to 19
public interface SchoolQueryUseCase {

List<SchoolSummary> getAll(Long gisuId);

SchoolInfo getById(Long schoolId);

PageResult<DeletableSchoolSummary> getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인터페이스가 단일 책임 원칙(SRP)을 위반하고 있습니다.

SchoolQueryUseCase 인터페이스에 3개의 서로 다른 쿼리 메서드가 포함되어 있습니다:

  1. getAll() - Gisu별 전체 School 조회
  2. getById() - 단일 School 상세 조회
  3. getList() - 페이지네이션 및 조건 기반 검색

또한 인터페이스 명명 규칙도 준수하지 않고 있습니다. Query UseCase는 Get{Domain}UseCase 패턴을 따라야 합니다.

PR 리뷰 중점사항에 대한 답변:
"인터페이스 수가 많아진 점"에 대해 우려하셨는데, 단일 책임 원칙에 따라 인터페이스를 분리하는 것이 올바른 설계입니다. 각 UseCase가 명확한 책임을 가지면 다음 장점이 있습니다:

  • 변경의 영향 범위 최소화
  • 테스트 용이성 향상
  • 의존성 관리 개선
  • 더 명확한 비즈니스 의도 표현
🔎 권장하는 수정

다음과 같이 3개의 별도 UseCase 인터페이스로 분리하세요:

1. GetAllSchoolsByGisuUseCase.java

package com.umc.product.query.organization.application.port.in;

import com.umc.product.query.organization.application.port.in.dto.response.SchoolSummary;
import java.util.List;

public interface GetAllSchoolsByGisuUseCase {
    List<SchoolSummary> getAll(Long gisuId);
}

2. GetSchoolByIdUseCase.java

package com.umc.product.query.organization.application.port.in;

import com.umc.product.query.organization.application.port.in.dto.response.SchoolInfo;

public interface GetSchoolByIdUseCase {
    SchoolInfo getById(Long schoolId);
}

3. GetDeletableSchoolsUseCase.java

package com.umc.product.query.organization.application.port.in;

import com.umc.product.query.organization.application.port.in.dto.response.DeletableSchoolSummary;
import com.umc.product.query.organization.application.port.in.dto.response.SchoolDeleteSearchCondition;
import com.umc.product.common.dto.request.PageRequest;
import com.umc.product.common.dto.request.PageResult;

public interface GetDeletableSchoolsUseCase {
    PageResult<DeletableSchoolSummary> getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest);
}

Based on learnings, UseCase interfaces should have single responsibility with one use case per interface, and query usecases should follow Get{Domain}UseCase naming pattern. 코딩 가이드라인의 단일 책임 원칙(SRP)도 이를 요구합니다.

@kyeoungwoon kyeoungwoon changed the title [Feat] Organization 도메인, outbound port 설계 완료, inbound port 일부 설계 완료 [Feat] Organization 도메인 port 설계 및 프로젝트 구조 확정 Jan 6, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 24

Fix all issues with AI Agents 🤖
In @CLAUDE.md:
- Around line 177-180: The table's Port naming is inconsistent with UseCase
naming and existing code patterns; update the table to use a consistent
pattern—either rename the Port to match the UseCase verb-first style
(ManageSchoolPort) or explicitly state you follow the domain-first pattern used
elsewhere (SchoolManagePort) and adjust the text to match actual code (e.g.,
reference ChapterManagePort). Pick one option and update the table row so
UseCase (ManageSchoolUseCase) and Port (ManageSchoolPort OR SchoolManagePort)
follow the same convention and add a short clarifying note indicating which
convention is used across the codebase.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java:
- Around line 6-10: The CreateSchoolRequest record is missing validation
annotations for chapterId and remark; add @NotNull to the chapterId parameter
and either annotate remark with @NotBlank if it is required or mark it
explicitly nullable (e.g., @Nullable) if optional so that input validation is
consistent with other request DTOs; update the CreateSchoolRequest signature to
include these annotations on the chapterId and remark components.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java:
- Around line 7-13: UpdateSchoolRequest has inconsistent validation messages:
schoolId has a message but schoolName (@NotBlank) and chapterId (@NotNull) do
not. Add explicit, user-friendly message strings to the @NotBlank on schoolName
(e.g., "학교 이름은 필수입니다") and to the @NotNull on chapterId (e.g., "챕터 ID는 필수입니다")
so all required fields include meaningful validation messages; keep remark
unchanged if optional.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java:
- Around line 3-8: The CreateSchoolCommand record is missing validation and a
conversion helper; add javax.validation annotations to the record components
(e.g., annotate schoolName and chapterId with @NotNull and/or @NotBlank as
appropriate) to enforce required fields, and implement a toEntity() method on
CreateSchoolCommand that constructs and returns the corresponding School entity
(mapping schoolName, chapterId and remark), so callers can convert validated
commands into domain objects; update imports accordingly and keep the record
immutable.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java:
- Around line 3-10: Add Bean Validation annotations to the UpdateSchoolCommand
record so required fields are validated at the port boundary: annotate schoolId
and chapterId with @NotNull and schoolName with @NotBlank (leave remark optional
or annotate with @NotBlank only if it must be non-empty). Import the constraint
annotations (e.g. javax.validation.constraints.NotNull and NotBlank) and place
them directly on the record components in the UpdateSchoolCommand declaration to
preserve immutability and ensure validation is applied.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java:
- Around line 7-16: ManageSchoolUseCase currently groups multiple
responsibilities (create, update, delete, deleteAll) violating the
`{Action}{Domain}UseCase` and single-responsibility guideline; split it into
separate interfaces: implement CreateSchoolUseCase with Long
create(CreateSchoolCommand), UpdateSchoolUseCase with void
update(UpdateSchoolCommand), and DeleteSchoolUseCase with void delete(Long) and
void deleteAll(List<Long>), update any implementing classes to implement the new
interfaces instead of ManageSchoolUseCase, and remove or deprecate the original
ManageSchoolUseCase interface.

In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/DeletableSchoolSummary.java:
- Around line 12-14: Remove the unnecessary blank line inside the
DeletableSchoolSummary record body: update the record declaration
(DeletableSchoolSummary) to have no empty line in its braces (or remove the
empty braces entirely if no body is needed) so the record body is compact and
consistent.

In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java:
- Around line 3-7: SchoolDeleteSearchCondition currently allows both keyword and
chapterId to be null with no validation; add a compact constructor to enforce
business rules (e.g., require at least one non-null search parameter and/or
enforce keyword length limits). In the compact constructor of the record
SchoolDeleteSearchCondition validate that (keyword != null || chapterId != null)
and throw IllegalArgumentException if both are null, and if desired also
validate keyword length (e.g., reject keyword.length() > 100) to prevent
excessive input. Ensure the validations reference the record components keyword
and chapterId so they execute on construction.

In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java:
- Around line 11-12: SchoolInfo의 감사 필드 createdAt, updatedAt이 LocalDate로 선언되어 시간
정보가 손실되니 LocalDateTime으로 변경하세요; SchoolInfo 클래스에서 해당 필드 타입을 LocalDateTime으로 바꾸고,
이를 소비하는 모든 매핑/변환 코드(예: SchoolResponse.from(), 관련 UseCase 구현체 및 DTO 매퍼들)에서 필드 타입을
LocalDateTime에 맞게 조정해 시차/타임스탬프 정보가 보존되도록 수정하세요.

In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java:
- Around line 3-7: The SchoolSummary record lacks validation on its components;
annotate the record components schoolId and schoolName with @NotNull and add the
corresponding import (e.g., jakarta.validation.constraints.NotNull or
javax.validation.constraints.NotNull) so the record becomes validated at
construction time; update the record declaration for SchoolSummary to include
@NotNull on both Long schoolId and String schoolName and ensure your build
includes the validation API dependency.

In
@src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java:
- Around line 14-15: Remove the unnecessary blank line inside the interface body
of CentralOrganizationManagePort; open the interface declaration for
CentralOrganizationManagePort and delete the empty line so the interface
contains no extraneous blank lines between its braces.
- Around line 6-12: CentralOrganizationManagePort currently mixes read and write
concerns (findById, save, delete); split it into two interfaces: create
LoadCentralOrganizationPort with only Optional<CentralOrganization>
findById(Long id) and SaveCentralOrganizationPort (or
ManageCentralOrganizationPort) with CentralOrganization
save(CentralOrganization) and void delete(CentralOrganization), remove those
methods from CentralOrganizationManagePort, update all implementations and
injection sites to implement/depend on the new interfaces (replace usages of
CentralOrganizationManagePort in constructors, autowired fields, and
repositories with LoadCentralOrganizationPort and SaveCentralOrganizationPort
accordingly) so reads and writes follow ISP.

In
@src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java:
- Line 3: Remove the unnecessary blank line(s) in the ChapterSchoolManagePort
file to improve readability—open the file containing the ChapterSchoolManagePort
interface and delete any extra empty lines (e.g., leading/trailing or
consecutive blank lines) so the interface declaration and its imports are
compact and consistently spaced.

In
@src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java:
- Around line 6-11: GisuManagePort currently combines save() and delete(),
violating ISP; split it into two focused ports SaveGisuPort (with Gisu
save(Gisu)) and DeleteGisuPort (with void delete(Gisu)), replace usages so
implementations and injection points implement only the port they need (update
classes that referenced GisuManagePort to depend on SaveGisuPort or
DeleteGisuPort), remove or deprecate the old GisuManagePort interface once
callers are migrated, and keep the same package and domain types (Gisu) to
minimize ripple changes.

In
@src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java:
- Line 3: Remove the unnecessary blank line(s) in the
StudyGroupMemberManagePort.java file to improve readability; specifically,
delete the extra empty line(s) at the top of the file so the
package/import/interface declaration for StudyGroupMemberManagePort appears
without leading blank lines.

In
@src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java:
- Around line 8-15: The port methods in LoadSchoolPort must support pagination
to match GetSchoolUseCase.getList; change the signatures of findAll() and
findByChapter(Chapter) to accept a PageRequest parameter and return a
PageResult<School> (or a paged container compatible with
PageResult<DeletableSchoolSummary> used by the use case) so the UseCase can
request paged data; update the interface methods findAll(PageRequest
pageRequest) and findByChapter(Chapter chapter, PageRequest pageRequest) and
ensure implementations and callers (e.g., GetSchoolUseCase.getList) are updated
to use the paged return type and forwarding PageRequest.

In @src/main/java/com/umc/product/organization/domain/CentralOrganization.java:
- Around line 18-49: Add a domain method on CentralOrganization to allow
controlled updates instead of exposing setters: implement a public
updateName(String name) method in CentralOrganization that performs the same
validation as validate (check null/blank and throw new
BusinessException(Domain.COMMON, OrganizationErrorCode.ORGAN_NAME_REQUIRED) on
invalid input) and then sets this.name = name, keeping the validate(Gisu,
String) method for construction-only checks.

In @src/main/java/com/umc/product/organization/domain/Chapter.java:
- Around line 33-47: The validate logic in Chapter currently throws
OrganizationErrorCode.ORGAN_NAME_REQUIRED for a missing chapter name; change
this to a more specific error by adding a CHAPTER_NAME_REQUIRED enum constant to
OrganizationErrorCode and update the validate(Gisu gisu, String name) method
(used by the Chapter(@Builder) constructor) to throw
OrganizationErrorCode.CHAPTER_NAME_REQUIRED instead of ORGAN_NAME_REQUIRED;
ensure any tests or usages expecting the old code are updated accordingly.

In @src/main/java/com/umc/product/organization/domain/ChapterSchool.java:
- Around line 27-33: ChapterSchool currently allows duplicate (chapter, school)
pairs at the DB level; add a unique constraint to the ChapterSchool entity so
the combination of chapter_id and school_id is enforced as unique (e.g.,
annotate the ChapterSchool class with @Table(uniqueConstraints =
@UniqueConstraint(columnNames = {"chapter_id","school_id"})) ensuring the column
names match the @JoinColumn names on the chapter and school fields), and create
a corresponding DB migration to add the unique index for existing data.

In @src/main/java/com/umc/product/organization/domain/School.java:
- Around line 30-36: Constructor builder parameter name "domain" does not match
the field name "emailDomain", causing potential confusion; update the @Builder
constructor for School to use a matching parameter name (e.g., rename parameter
"domain" to "emailDomain") and adjust the validate call to pass the renamed
parameter (validate(name, emailDomain)), ensuring assignment this.emailDomain =
emailDomain remains correct; keep the rest of the constructor logic intact.
- Around line 15-47: The School entity lacks domain methods for updating state,
so add mutator methods on the School class (e.g., updateName, updateEmailDomain,
updateLogoImage) that perform the same validations as validate() and then set
the fields (name, emailDomain, logoImageId) instead of exposing setters; ensure
updateName and updateEmailDomain throw BusinessException with
OrganizationErrorCode.SCHOOL_NAME_REQUIRED / SCHOOL_DOMAIN_REQUIRED when
invalid, and keep updateLogoImage to simply assign the new logoImageId.

In @src/main/java/com/umc/product/organization/domain/StudyGroup.java:
- Around line 138-144: The findMemberByChallengerId method throws a
BusinessException without indicating which challengerId was missing; update the
exception so it includes the challengerId (e.g., pass a message or detail
containing the challengerId to BusinessException when throwing in
findMemberByChallengerId) or use an existing BusinessException constructor that
accepts parameters to include the id alongside Domain.COMMON and
OrganizationErrorCode.STUDY_GROUP_MEMBER_NOT_FOUND for clearer debugging.
- Around line 118-121: The hasMember method lacks null defensive checks for the
challengerId and may NPE if member.getChallengerId() is null; update hasMember
to return false immediately when challengerId is null and use a null-safe
comparison (e.g., Objects.equals or explicit null checks) between challengerId
and member.getChallengerId() while iterating studyGroupMembers to avoid any
NullPointerException.

In
@src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java:
- Around line 12-32: The enum constants in OrganizationErrorCode use a
misspelled error-code prefix "ORGANIZAITON" that should be "ORGANIZATION", and
the file ends with a duplicated semicolon after the last constant; update every
constant string value (e.g., GISU_REQUIRED, ORGAN_NAME_REQUIRED, ...
STUDY_GROUP_MEMBER_NOT_FOUND) to replace "ORGANIZAITON" with "ORGANIZATION" and
remove the extra semicolon so the enum terminator is a single semicolon.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 668b8ca and aa6c4f8.

📒 Files selected for processing (35)
  • CLAUDE.md
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
🧰 Additional context used
📓 Path-based instructions (8)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java
  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java
  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSummary.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/DeletableSchoolSummary.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
**/domain/**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

State changes in Entity classes must be performed through domain methods, not via setters

Files:

  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/StudyGroupMember.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
  • src/main/java/com/umc/product/organization/domain/StudyGroup.java
**/*adapter/in/web/dto/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
**/*adapter/in/web/dto/request/**Request.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request DTOs must include @Valid, @NotNull, and @notblank validation annotations; include toCommand() conversion method to command record

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
**/*application/port/in/**/*{Command,Query}.java

📄 CodeRabbit inference engine (CLAUDE.md)

Command and Query records should be immutable with @NotNull validation and include conversion method toCommand() or toEntity()

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
**/*adapter/in/web/dto/response/**Response.java

📄 CodeRabbit inference engine (CLAUDE.md)

Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
🧠 Learnings (23)
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Load Port methods must use Optional for single object returns and avoid returning Response DTOs

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/out/persistence/**PersistenceAdapter.java : Persistence adapters (in adapter/out/persistence/) must implement Load{Domain}Port and Save{Domain}Port interfaces and delegate to JPA repositories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/query/LoadSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/GisuManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/CentralOrganizationManagePort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain Entity references are forbidden; only store ID fields (Column private Long {domain}Id), access other domains via UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/domain/ChapterSchool.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/domain/CentralOrganization.java
  • src/main/java/com/umc/product/organization/domain/Chapter.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Response.java : Response DTO classes should use record type and provide static from() method for conversion from domain/info objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/response/**Response.java : Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/exception/**/*.java : Exception handling must use BusinessException, ErrorCode enums, and GlobalExceptionHandler in global/exception/ package

Applied to files:

  • src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/UpdateSchoolInfo.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Cross-domain communication from services must use Query UseCase interfaces (GetUserInfoUseCase, GetOrganizationUseCase) for accessing other domains, not direct repository access

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/**Info.java : Info DTOs for cross-domain responses must use {Domain}Info naming pattern and be immutable records

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolInfo.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*Entity.java : Entity classes must use NoArgsConstructor(access = AccessLevel.PROTECTED), not Setter, and reference only IDs of other domains instead of direct entity references

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*.java : State changes in Entity classes must be performed through domain methods, not via setters

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Entity state changes must only be modified through domain methods, not direct field assignment

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
🧬 Code graph analysis (2)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (6)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (6)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/organization/domain/School.java (1)
  • Entity (15-47)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
🔇 Additional comments (23)
src/main/java/com/umc/product/organization/application/port/out/command/ChapterManagePort.java (1)

6-12: Manage 패턴과 ISP 원칙 간의 트레이드오프를 확인해주세요.

ChapterManagePortsavedelete 메서드를 하나의 인터페이스에 통합하고 있습니다. 이는 CLAUDE.md에 새로 추가된 "Manage 통합 옵션" 패턴을 따르는 것으로 보입니다.

하지만 기존 학습된 규칙에서는 "Port 인터페이스는 ISP(Interface Segregation Principle)를 따라 Load와 Save 포트로 분리되어야 한다"고 명시되어 있습니다.

PR 목적에서 "인터페이스를 용도에 맞게 분리하면서 인터페이스 수가 많아진 점"에 대한 의견을 요청하셨는데, 이 새로운 Manage 패턴이 그에 대한 해결책으로 보입니다.

확인 필요 사항:

  1. Manage 패턴 도입이 의도적인 설계 결정인지
  2. 기존 ISP 원칙 대신 CUD 통합 패턴을 프로젝트 표준으로 채택할 것인지
  3. Load 작업은 여전히 별도 Load{Domain}Port로 분리되는지

구현 자체는 명확하고 일관성 있게 작성되었습니다. 다만 아키텍처 방향성에 대한 팀 차원의 합의가 필요해 보입니다.

Based on learnings, 기존 가이드라인과의 차이점을 확인하기 위해 검증을 요청합니다.

src/main/java/com/umc/product/organization/domain/Chapter.java (1)

18-31: LGTM! 엔티티 구조가 올바르게 설계되었습니다.

JPA 엔티티 선언과 필드 매핑이 적절합니다:

  • @NoArgsConstructor(access = AccessLevel.PROTECTED)로 JPA 프록시 생성 지원
  • @ManyToOne(fetch = FetchType.LAZY)로 N+1 문제 방지
  • 다른 도메인 엔티티들(CentralOrganization, StudyGroup 등)과 일관된 패턴 사용
src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolSummaryResponse.java (1)

5-15: LGTM! Response DTO가 가이드라인을 완벽히 따르고 있습니다.

Response DTO 구현이 우수합니다:

  • record 타입으로 불변성 보장
  • from() 정적 팩토리 메서드로 도메인 DTO에서 변환
  • 네이밍 패턴({Domain}{Purpose}Response) 준수
  • 올바른 패키지 위치(adapter/in/web/dto/response)

학습된 가이드라인을 정확히 따르고 있습니다.

Based on learnings, Response DTOs should use records with static from() methods.

src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupPort.java (1)

6-11: LGTM! 포트 인터페이스 설계가 적절합니다.

  • Interface Segregation Principle을 준수하여 조회 전용 포트로 분리되어 있습니다.
  • 단일 객체 반환 시 Optional을 사용하여 null-safety를 보장합니다.
  • 기술 중립적인 메서드명(findById, findByName)을 사용하고 있습니다.
  • 도메인 엔티티를 반환 타입으로 사용하여 헥사고날 아키텍처 원칙을 따릅니다.

Based on learnings, Load port interfaces should use Optional for single object returns and technology-neutral method names.

src/main/java/com/umc/product/organization/application/port/out/command/SchoolManagePort.java (1)

6-11: LGTM! 커맨드 포트 설계가 적절합니다.

  • 조회 작업과 분리하여 커맨드 전용 포트로 설계되어 ISP를 준수합니다.
  • 도메인 엔티티를 파라미터와 반환 타입으로 사용하여 계층 분리가 명확합니다.
  • savedelete 메서드명이 기술 중립적입니다.

Based on learnings, port interfaces should be separated following Interface Segregation Principle and use domain entities.

src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupManagePort.java (1)

6-10: LGTM! 커맨드 포트 설계가 적절합니다.

포트 인터페이스가 ISP를 준수하며 도메인 엔티티를 사용하고 있습니다. SchoolManagePort와 동일한 패턴을 따라 일관성이 유지됩니다.

Based on learnings, command ports should be separated from query ports and use domain entities.

src/main/java/com/umc/product/organization/application/port/out/query/LoadStudyGroupMemberPort.java (1)

8-13: LGTM! 조회 포트 설계가 적절합니다.

  • 단일 객체 조회 시 Optional을 반환하여 null-safety를 보장합니다.
  • 다중 객체 조회 시 List를 반환하는 것이 적절합니다.
  • 도메인 엔티티를 파라미터로 받아 관계 조회를 수행하는 설계가 명확합니다.
  • 기술 중립적인 메서드명을 사용합니다.

Based on learnings, Load ports should use Optional for single returns and avoid Response DTOs.

src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java (1)

11-13: LGTM! Command 변환 메서드가 적절합니다.

Request DTO에서 Command 객체로 변환하는 toCommand() 메서드가 올바르게 구현되어 있습니다.

Based on learnings, Request DTOs should provide toCommand() method to convert to UseCase command objects.

src/main/java/com/umc/product/organization/application/port/out/query/LoadGisuPort.java (1)

7-12: Port 인터페이스 설계가 잘 되어 있습니다.

ISP 원칙에 따라 Load 포트를 별도로 분리하고, Optional과 technology-neutral 메서드명을 사용한 점이 좋습니다. 다만 findAll() 메서드가 페이지네이션 없이 모든 기수 데이터를 반환하므로, 향후 데이터가 증가할 경우 성능 이슈가 발생할 수 있습니다.

Gisu 데이터의 예상 규모를 확인하고, 필요시 페이지네이션 지원을 고려해주세요.

Based on learnings, Load Port interfaces는 Optional을 사용하고 기술 중립적 메서드명을 사용해야 하며, 이는 잘 준수되었습니다.

src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterPort.java (1)

8-15: 페이지네이션 지원 검토가 필요합니다.

Port 인터페이스 설계는 ISP 원칙과 hexagonal architecture를 잘 따르고 있습니다. 하지만 findAll()findAllByGisu() 메서드가 모든 Chapter 데이터를 한 번에 반환하므로, 지부 수가 많아질 경우 메모리와 성능에 부담이 될 수 있습니다.

전국 지부 데이터의 예상 규모를 확인하고, PageRequest/PageResult를 활용한 페이지네이션 메서드 추가를 고려해주세요.

Based on learnings, Load Port는 Optional과 List를 반환하며 기술 중립적 명명을 사용해야 하는데, 이는 잘 지켜졌습니다.

src/main/java/com/umc/product/organization/application/port/out/command/ChapterSchoolManagePort.java (1)

6-10: Command Port 설계가 적절합니다.

ISP 원칙에 따라 command 작업(save, delete)을 별도 포트로 분리한 것이 좋습니다. ManagePort 네이밍은 save와 delete를 모두 포함하므로 합리적한 선택입니다.

Based on learnings, Port 인터페이스는 Load와 Save(또는 Manage)로 분리되어야 하며, 이는 잘 준수되었습니다.

src/main/java/com/umc/product/organization/application/port/out/command/StudyGroupMemberManagePort.java (1)

6-11: Aggregate 패턴을 위한 Command Port 설계가 우수합니다.

PR objectives에서 언급된 StudyGroupMember aggregate 패턴 적용에 맞춰, ISP 원칙에 따라 command 작업을 별도 포트로 분리한 것이 좋습니다. ManagePort 네이밍도 save와 delete를 모두 포함하므로 적절합니다.

PR 리뷰 중점사항으로 제기된 "인터페이스 수가 많아진 점"은 ISP(Interface Segregation Principle)를 올바르게 적용한 결과이므로 긍정적입니다. 각 포트가 단일 책임을 가지고 있어 유지보수성과 테스트 용이성이 향상됩니다.

Based on learnings, Port 인터페이스는 용도에 따라 Load와 Manage로 분리되어야 하며, 이는 잘 준수되었습니다.

src/main/java/com/umc/product/organization/domain/ChapterSchool.java (2)

35-40: 생성자 검증 로직이 잘 구현되었습니다

빌더 패턴에서 객체 생성 전 검증을 수행하여 도메인 불변성을 보장하는 좋은 패턴입니다.


42-49: 에러 코드가 정의되어 있으므로 추가 수정이 필요 없습니다.

OrganizationErrorCode.CHAPTER_REQUIREDSCHOOL_REQUIRED 모두 정의되어 있습니다. 검증 로직은 생성자에서 필수값을 확인하는 적절한 도메인 메서드 패턴을 따르고 있으며, BusinessException을 통해 중앙화된 예외 처리를 구현하고 있습니다.

src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java (1)

14-16: LGTM: toCommand() 변환 메서드

Request DTO에서 Command 객체로의 변환이 올바르게 구현되었습니다.

src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolResponse.java (2)

7-15: LGTM: Response DTO 구조

필드가 논리적으로 잘 그룹화되어 있으며(챕터 정보, 학교 정보, 메타데이터), record 타입을 사용하여 불변성을 보장하고 있습니다.


16-26: LGTM: from() 팩토리 메서드

SchoolInfo에서 SchoolResponse로의 변환이 명확하고 올바르게 구현되었습니다. 정적 팩토리 메서드 패턴을 잘 따르고 있습니다.

src/main/java/com/umc/product/organization/domain/Gisu.java (1)

1-63: 도메인 엔티티 구조가 잘 설계되었습니다.

  • JPA를 위한 protected 기본 생성자 사용
  • Builder를 통한 유효성 검증
  • setter 없이 도메인 메서드(active(), inactive())를 통한 상태 변경

코딩 가이드라인에 부합합니다.

src/main/java/com/umc/product/organization/application/port/out/query/LoadChapterSchoolPort.java (1)

9-16: Port 인터페이스가 가이드라인에 잘 맞게 설계되었습니다.

  • 인터페이스 분리 원칙(ISP) 준수: Load/Save 포트 분리
  • 단일 객체 반환 시 Optional 사용
  • 기술 중립적인 메서드명 사용 (findById, findByChapterAndSchool)
  • 도메인 엔티티를 반환 타입으로 사용

Based on learnings, 포트 인터페이스 설계 원칙을 잘 따르고 있습니다.

src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java (1)

12-19: Query UseCase 인터페이스가 적절히 설계되었습니다.

  • Get{Domain}UseCase 네이밍 패턴 준수
  • 도메인 엔티티 대신 DTO 반환 (SchoolSummary, SchoolInfo, PageResult)
  • query 패키지에 위치

선택적 고려사항: 학습 자료에 따르면 UseCase 인터페이스는 단일 책임을 가져야 합니다. 현재 3개의 메서드가 있는데, 읽기 작업으로 그룹화된 것은 합리적이지만 인터페이스 분리 여부는 팀 컨벤션에 맞게 결정해주세요.

src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)

1-51: LGTM! 도메인 엔티티 구조가 잘 설계되었습니다.

  • DDD 원칙에 따라 다른 도메인(Challenger)의 Entity를 직접 참조하지 않고 ID만 저장하는 방식이 적절합니다.
  • Builder 패턴과 생성자 내 검증 로직으로 불변성을 잘 유지하고 있습니다.
  • @ManyToOne(fetch = FetchType.LAZY) 설정으로 N+1 문제 방지를 고려했습니다.
  • 프로젝트 내 다른 도메인 엔티티들(Chapter, School, ChapterSchool 등)과 일관된 구조를 따르고 있습니다.
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)

22-58: Aggregate Root 패턴이 잘 적용되었습니다.

  • @OneToMany(cascade = ALL, orphanRemoval = true) 설정으로 멤버 생명주기 관리가 적절합니다.
  • Builder 패턴과 생성자 검증으로 엔티티 일관성을 유지하고 있습니다.
  • 프로젝트 내 다른 도메인 엔티티들과 일관된 구조를 따르고 있습니다.
src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java (1)

8-10: 예외 처리 구조가 가이드라인에 맞게 구현되었습니다.

BaseCode 인터페이스를 구현하고 BusinessException과 함께 사용되는 구조가 프로젝트의 예외 처리 패턴을 따르고 있습니다. Based on learnings, 예외 처리는 BusinessException, ErrorCode enum, GlobalExceptionHandler를 사용해야 합니다.

Comment on lines 6 to 10
public record CreateSchoolRequest(
@NotBlank String schoolName,
Long chapterId,
String remark
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

입력 검증 어노테이션을 추가해주세요.

chapterIdremark 필드에 적절한 검증 어노테이션이 누락되어 있습니다.

  • chapterId: School이 Chapter에 소속되어야 한다면 @NotNull 어노테이션이 필요합니다.
  • remark: 선택 필드라면 명시적으로 nullable 처리하거나, 필수라면 @NotBlank 어노테이션을 추가해야 합니다.

As per coding guidelines, Request DTOs must include validation annotations for proper input validation.

🔎 제안하는 수정안
+import jakarta.validation.constraints.NotNull;
+
 public record CreateSchoolRequest(
         @NotBlank String schoolName,
+        @NotNull Long chapterId,
         String remark
 ) {

또는 remark가 필수 필드인 경우:

 public record CreateSchoolRequest(
         @NotBlank String schoolName,
+        @NotNull Long chapterId,
-        String remark
+        @NotBlank String remark
 ) {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
around lines 6-10, The CreateSchoolRequest record is missing validation
annotations for chapterId and remark; add @NotNull to the chapterId parameter
and either annotate remark with @NotBlank if it is required or mark it
explicitly nullable (e.g., @Nullable) if optional so that input validation is
consistent with other request DTOs; update the CreateSchoolRequest signature to
include these annotations on the chapterId and remark components.

Comment on lines 3 to 10
public record UpdateSchoolCommand(
Long schoolId,
String schoolName,
Long chapterId,
String remark
) {

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Command 레코드에 유효성 검증 어노테이션 추가 필요

Command 객체는 포트 경계에서 유효성을 보장해야 합니다. 필수 필드에 @NotNull, @NotBlank 어노테이션을 추가해 주세요.

코딩 가이드라인에 따르면, Command 레코드는 불변성과 함께 NotNull 검증을 포함해야 합니다.

🔎 제안하는 수정
 package com.umc.product.organization.application.port.in.command.dto;
 
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
 public record UpdateSchoolCommand(
+        @NotNull(message = "학교 ID는 필수입니다")
         Long schoolId,
+        @NotBlank(message = "학교명은 필수입니다")
         String schoolName,
+        @NotNull(message = "챕터 ID는 필수입니다")
         Long chapterId,
         String remark
 ) {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
around lines 3-10, Add Bean Validation annotations to the UpdateSchoolCommand
record so required fields are validated at the port boundary: annotate schoolId
and chapterId with @NotNull and schoolName with @NotBlank (leave remark optional
or annotate with @NotBlank only if it must be non-empty). Import the constraint
annotations (e.g. javax.validation.constraints.NotNull and NotBlank) and place
them directly on the record components in the UpdateSchoolCommand declaration to
preserve immutability and ensure validation is applied.

Comment on lines +12 to +14
) {

}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

레코드 본문 내 불필요한 빈 줄을 제거하세요.

레코드 본문에 빈 줄이 불필요합니다. 코드 일관성을 위해 제거를 권장합니다.

🔎 제안하는 수정
 ) {
-
 }
📝 Committable suggestion

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

Suggested change
) {
}
) {
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/DeletableSchoolSummary.java
around lines 12-14, Remove the unnecessary blank line inside the
DeletableSchoolSummary record body: update the record declaration
(DeletableSchoolSummary) to have no empty line in its braces (or remove the
empty braces entirely if no body is needed) so the record body is compact and
consistent.

Comment on lines +3 to +7
public record SchoolDeleteSearchCondition(
String keyword,
Long chapterId
) {
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

검색 조건 검증 고려사항

SchoolDeleteSearchCondition은 검색 조건 DTO로, 두 필드 모두 선택적(nullable)인 것으로 보입니다. 그러나 비즈니스 로직에 따라 다음을 고려해볼 수 있습니다:

  1. 최소 검색 조건 필요 여부: keywordchapterId 모두 null인 경우를 허용할 것인지
  2. keyword 길이 제한: 검색 성능을 위한 최소/최대 길이 제한
  3. 유효성 검증: compact constructor에서 비즈니스 규칙 검증
📝 선택적 검증 예시
public record SchoolDeleteSearchCondition(
        String keyword,
        Long chapterId
) {
    public SchoolDeleteSearchCondition {
        // 예: 둘 다 null이면 전체 조회가 되어 성능 이슈 가능
        if (keyword == null && chapterId == null) {
            throw new IllegalArgumentException("최소 하나의 검색 조건이 필요합니다");
        }
        // 또는 keyword 길이 검증
        if (keyword != null && keyword.length() > 100) {
            throw new IllegalArgumentException("검색어는 100자를 초과할 수 없습니다");
        }
    }
}

비즈니스 요구사항에 따라 적용 여부를 결정하시면 됩니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolDeleteSearchCondition.java
around lines 3-7, SchoolDeleteSearchCondition currently allows both keyword and
chapterId to be null with no validation; add a compact constructor to enforce
business rules (e.g., require at least one non-null search parameter and/or
enforce keyword length limits). In the compact constructor of the record
SchoolDeleteSearchCondition validate that (keyword != null || chapterId != null)
and throw IllegalArgumentException if both are null, and if desired also
validate keyword length (e.g., reject keyword.length() > 100) to prevent
excessive input. Ensure the validations reference the record components keyword
and chapterId so they execute on construction.

Comment on lines 15 to 47
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class School {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private String emailDomain;

private Long logoImageId;

@Builder
private School(String name, String domain, Long logoImageId) {
validate(name, domain);
this.name = name;
this.emailDomain = domain;
this.logoImageId = logoImageId;
}

private static void validate(String name, String domain) {
if (name == null || name.isBlank()) {
throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_NAME_REQUIRED);
}
if (domain == null || domain.isBlank()) {
throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_DOMAIN_REQUIRED);
}
}

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

상태 변경을 위한 도메인 메서드 추가 필요

PR에 UpdateSchoolCommand가 존재하는 것으로 보아 School 업데이트 기능이 필요합니다. 현재 생성 후 상태 변경 방법이 없으므로 도메인 메서드를 추가해야 합니다.

코딩 가이드라인에 따르면 엔티티 상태 변경은 setter가 아닌 도메인 메서드를 통해 수행해야 합니다.

🔎 도메인 메서드 추가 예시
public void updateName(String name) {
    if (name == null || name.isBlank()) {
        throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_NAME_REQUIRED);
    }
    this.name = name;
}

public void updateEmailDomain(String emailDomain) {
    if (emailDomain == null || emailDomain.isBlank()) {
        throw new BusinessException(Domain.COMMON, OrganizationErrorCode.SCHOOL_DOMAIN_REQUIRED);
    }
    this.emailDomain = emailDomain;
}

public void updateLogoImage(Long logoImageId) {
    this.logoImageId = logoImageId;
}
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/School.java around lines
15-47, The School entity lacks domain methods for updating state, so add mutator
methods on the School class (e.g., updateName, updateEmailDomain,
updateLogoImage) that perform the same validations as validate() and then set
the fields (name, emailDomain, logoImageId) instead of exposing setters; ensure
updateName and updateEmailDomain throw BusinessException with
OrganizationErrorCode.SCHOOL_NAME_REQUIRED / SCHOOL_DOMAIN_REQUIRED when
invalid, and keep updateLogoImage to simply assign the new logoImageId.

Comment on lines 30 to 36
@Builder
private School(String name, String domain, Long logoImageId) {
validate(name, domain);
this.name = name;
this.emailDomain = domain;
this.logoImageId = logoImageId;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

빌더 파라미터명과 필드명 일관성 확보 필요

빌더 파라미터 domain과 필드명 emailDomain이 다릅니다. 이로 인해 빌더 사용 시 혼란이 발생할 수 있습니다.

🔎 제안하는 수정
 @Builder
-private School(String name, String domain, Long logoImageId) {
-    validate(name, domain);
+private School(String name, String emailDomain, Long logoImageId) {
+    validate(name, emailDomain);
     this.name = name;
-    this.emailDomain = domain;
+    this.emailDomain = emailDomain;
     this.logoImageId = logoImageId;
 }
📝 Committable suggestion

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

Suggested change
@Builder
private School(String name, String domain, Long logoImageId) {
validate(name, domain);
this.name = name;
this.emailDomain = domain;
this.logoImageId = logoImageId;
}
@Builder
private School(String name, String emailDomain, Long logoImageId) {
validate(name, emailDomain);
this.name = name;
this.emailDomain = emailDomain;
this.logoImageId = logoImageId;
}
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/School.java around lines
30-36, Constructor builder parameter name "domain" does not match the field name
"emailDomain", causing potential confusion; update the @Builder constructor for
School to use a matching parameter name (e.g., rename parameter "domain" to
"emailDomain") and adjust the validate call to pass the renamed parameter
(validate(name, emailDomain)), ensuring assignment this.emailDomain =
emailDomain remains correct; keep the rest of the constructor logic intact.

Comment on lines +118 to +121
public boolean hasMember(Long challengerId) {
return studyGroupMembers.stream()
.anyMatch(member -> member.getChallengerId().equals(challengerId));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

hasMember 메서드에 null 방어 코드 추가 권장

challengerId가 null로 전달될 경우 의도치 않은 동작이 발생할 수 있습니다. 방어적 프로그래밍을 위해 null 체크를 추가하는 것이 좋습니다.

🔎 수정 제안
 public boolean hasMember(Long challengerId) {
+    if (challengerId == null) {
+        return false;
+    }
     return studyGroupMembers.stream()
             .anyMatch(member -> member.getChallengerId().equals(challengerId));
 }
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/StudyGroup.java around
lines 118-121, The hasMember method lacks null defensive checks for the
challengerId and may NPE if member.getChallengerId() is null; update hasMember
to return false immediately when challengerId is null and use a null-safe
comparison (e.g., Objects.equals or explicit null checks) between challengerId
and member.getChallengerId() while iterating studyGroupMembers to avoid any
NullPointerException.

Comment on lines +138 to +144
private StudyGroupMember findMemberByChallengerId(Long challengerId) {
return studyGroupMembers.stream()
.filter(member -> member.getChallengerId().equals(challengerId))
.findFirst()
.orElseThrow(
() -> new BusinessException(Domain.COMMON, OrganizationErrorCode.STUDY_GROUP_MEMBER_NOT_FOUND));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

예외 처리 시 세부 정보 포함 고려

findMemberByChallengerId에서 예외 발생 시 어떤 challengerId를 찾지 못했는지 알기 어렵습니다. 디버깅 편의를 위해 예외 메시지에 ID를 포함하는 것을 권장합니다. 단, 민감 정보가 아닌 경우에만 적용하세요.

🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/StudyGroup.java around
lines 138-144, The findMemberByChallengerId method throws a BusinessException
without indicating which challengerId was missing; update the exception so it
includes the challengerId (e.g., pass a message or detail containing the
challengerId to BusinessException when throwing in findMemberByChallengerId) or
use an existing BusinessException constructor that accepts parameters to include
the id alongside Domain.COMMON and
OrganizationErrorCode.STUDY_GROUP_MEMBER_NOT_FOUND for clearer debugging.

Comment on lines +12 to +32
GISU_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0001", "기수는 필수입니다."),
ORGAN_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0002", "조직 이름 설정은 필수입니다."),
SCHOOL_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0003", "학교는 필수입니다."),
CHAPTER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0004", "지부는 필수입니다."),


GISU_START_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0005", "기수 시작일은 필수입니다."),
GISU_END_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0006", "기수 종료일은 필수입니다."),
GISU_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0007", "기수 시작일은 종료일보다 이전이어야 합니다."),

SCHOOL_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0008", "학교 이름은 필수입니다."),
SCHOOL_DOMAIN_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0009", "학교 이메일 도메인은 필수입니다."),

STUDY_GROUP_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0010", "스터디 그룹 이름은 필수입니다."),
STUDY_GROUP_LEADER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0011", "스터디 그룹 리더는 필수입니다."),

STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0012", "스터디 그룹은 필수입니다."),
STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0013", "스터디 그룹 멤버는 필수입니다."),
CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0014", "챌린저 ID는 필수입니다."),
STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0015", "이미 존재하는 스터디 그룹 멤버입니다."),
STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

에러 코드 접두어 오타 및 문법 오류 수정 필요

  1. 모든 에러 코드에서 ORGANIZAITONORGANIZATION으로 수정되어야 합니다 (오타).
  2. Line 32에서 세미콜론이 중복(;;)되어 컴파일 오류가 발생합니다.
🔎 수정 제안
-    GISU_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0001", "기수는 필수입니다."),
-    ORGAN_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0002", "조직 이름 설정은 필수입니다."),
-    SCHOOL_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0003", "학교는 필수입니다."),
-    CHAPTER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0004", "지부는 필수입니다."),
+    GISU_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0001", "기수는 필수입니다."),
+    ORGAN_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0002", "조직 이름 설정은 필수입니다."),
+    SCHOOL_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0003", "학교는 필수입니다."),
+    CHAPTER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0004", "지부는 필수입니다."),
 
 
-    GISU_START_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0005", "기수 시작일은 필수입니다."),
-    GISU_END_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0006", "기수 종료일은 필수입니다."),
-    GISU_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0007", "기수 시작일은 종료일보다 이전이어야 합니다."),
+    GISU_START_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0005", "기수 시작일은 필수입니다."),
+    GISU_END_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0006", "기수 종료일은 필수입니다."),
+    GISU_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "ORGANIZATION-0007", "기수 시작일은 종료일보다 이전이어야 합니다."),
 
-    SCHOOL_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0008", "학교 이름은 필수입니다."),
-    SCHOOL_DOMAIN_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0009", "학교 이메일 도메인은 필수입니다."),
+    SCHOOL_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0008", "학교 이름은 필수입니다."),
+    SCHOOL_DOMAIN_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0009", "학교 이메일 도메인은 필수입니다."),
 
-    STUDY_GROUP_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0010", "스터디 그룹 이름은 필수입니다."),
-    STUDY_GROUP_LEADER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0011", "스터디 그룹 리더는 필수입니다."),
+    STUDY_GROUP_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0010", "스터디 그룹 이름은 필수입니다."),
+    STUDY_GROUP_LEADER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0011", "스터디 그룹 리더는 필수입니다."),
 
-    STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0012", "스터디 그룹은 필수입니다."),
-    STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0013", "스터디 그룹 멤버는 필수입니다."),
-    CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0014", "챌린저 ID는 필수입니다."),
-    STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0015", "이미 존재하는 스터디 그룹 멤버입니다."),
-    STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
+    STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0012", "스터디 그룹은 필수입니다."),
+    STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0013", "스터디 그룹 멤버는 필수입니다."),
+    CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0014", "챌린저 ID는 필수입니다."),
+    STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZATION-0015", "이미 존재하는 스터디 그룹 멤버입니다."),
+    STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZATION-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");
📝 Committable suggestion

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

Suggested change
GISU_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0001", "기수는 필수입니다."),
ORGAN_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0002", "조직 이름 설정은 필수입니다."),
SCHOOL_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0003", "학교는 필수입니다."),
CHAPTER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0004", "지부는 필수입니다."),
GISU_START_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0005", "기수 시작일은 필수입니다."),
GISU_END_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0006", "기수 종료일은 필수입니다."),
GISU_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0007", "기수 시작일은 종료일보다 이전이어야 합니다."),
SCHOOL_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0008", "학교 이름은 필수입니다."),
SCHOOL_DOMAIN_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0009", "학교 이메일 도메인은 필수입니다."),
STUDY_GROUP_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0010", "스터디 그룹 이름은 필수입니다."),
STUDY_GROUP_LEADER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0011", "스터디 그룹 리더는 필수입니다."),
STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0012", "스터디 그룹은 필수입니다."),
STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0013", "스터디 그룹 멤버는 필수입니다."),
CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0014", "챌린저 ID는 필수입니다."),
STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZAITON-0015", "이미 존재하는 스터디 그룹 멤버입니다."),
STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZAITON-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");;
GISU_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0001", "기수는 필수입니다."),
ORGAN_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0002", "조직 이름 설정은 필수입니다."),
SCHOOL_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0003", "학교는 필수입니다."),
CHAPTER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0004", "지부는 필수입니다."),
GISU_START_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0005", "기수 시작일은 필수입니다."),
GISU_END_AT_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0006", "기수 종료일은 필수입니다."),
GISU_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "ORGANIZATION-0007", "기수 시작일은 종료일보다 이전이어야 합니다."),
SCHOOL_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0008", "학교 이름은 필수입니다."),
SCHOOL_DOMAIN_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0009", "학교 이메일 도메인은 필수입니다."),
STUDY_GROUP_NAME_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0010", "스터디 그룹 이름은 필수입니다."),
STUDY_GROUP_LEADER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0011", "스터디 그룹 리더는 필수입니다."),
STUDY_GROUP_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0012", "스터디 그룹은 필수입니다."),
STUDY_GROUP_MEMBER_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0013", "스터디 그룹 멤버는 필수입니다."),
CHALLENGER_ID_REQUIRED(HttpStatus.BAD_REQUEST, "ORGANIZATION-0014", "챌린저 ID는 필수입니다."),
STUDY_GROUP_MEMBER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "ORGANIZATION-0015", "이미 존재하는 스터디 그룹 멤버입니다."),
STUDY_GROUP_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "ORGANIZATION-0016", "스터디 그룹 멤버를 찾을 수 없습니다.");
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/exception/OrganizationErrorCode.java
around lines 12-32, The enum constants in OrganizationErrorCode use a misspelled
error-code prefix "ORGANIZAITON" that should be "ORGANIZATION", and the file
ends with a duplicated semicolon after the last constant; update every constant
string value (e.g., GISU_REQUIRED, ORGAN_NAME_REQUIRED, ...
STUDY_GROUP_MEMBER_NOT_FOUND) to replace "ORGANIZAITON" with "ORGANIZATION" and
remove the extra semicolon so the enum terminator is a single semicolon.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Fix all issues with AI Agents 🤖
In @CLAUDE.md:
- Around line 173-181: The Port naming in the documentation table conflicts with
the code: the table lists SchoolManagePort but the code uses ManageSchoolPort,
ManageChapterSchoolPort, ManageGisuPort; update the table to use the actual
implemented names (e.g., ManageSchoolPort) or add a short note declaring the
project's chosen convention ("Manage" prefix before entity, e.g.,
ManageSchoolPort) and rename the examples accordingly so docs and code match
(ensure references to ManageSchoolPort, ManageChapterSchoolPort, ManageGisuPort
are used consistently).

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java:
- Around line 14-15: Remove the unnecessary blank lines at the end of the
interface declaration for ManageCentralOrganizationPort so the file ends
immediately after the closing brace; ensure there is only a single newline after
the closing '}' of the ManageCentralOrganizationPort interface and no extra
empty lines following it.
- Around line 6-13: The ManageCentralOrganizationPort currently mixes query and
command concerns: remove the findById method from the
ManageCentralOrganizationPort interface (leaving only save(CentralOrganization)
and delete(CentralOrganization)), create a new LoadCentralOrganizationPort
interface that declares Optional<CentralOrganization> findById(Long id), and
update any implementations, injection points, and usages to depend on
LoadCentralOrganizationPort for reads and ManageCentralOrganizationPort for
writes so the package/name and ISP are consistent.

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java:
- Around line 2-3: Remove the unnecessary blank lines in the
ManageChapterPort.java file to improve readability; specifically delete the
extra empty lines at the top of the file and the blank lines around lines 7-8
(inside the ManageChapterPort interface) so the interface declaration and its
members are compact and do not contain spurious blank lines.
- Around line 6-12: ManageChapterPort currently combines save and delete
operations violating ISP and naming conventions; split it into two ports by
extracting the save(Chapter chapter) method into a SaveChapterPort interface and
the delete(Chapter chapter) method into a DeleteChapterPort interface (or at
minimum rename to SaveChapterPort/DeleteChapterPort to follow
Save{Domain}Port/Delete{Domain}Port patterns), update any classes referencing
ManageChapterPort to depend on the specific SaveChapterPort or
DeleteChapterPort, and remove the save/delete methods from the original
ManageChapterPort.

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java:
- Line 3: Remove the unnecessary blank line before the import statements in the
ManageChapterSchoolPort.java file; open the ManageChapterSchoolPort interface
declaration and delete the extra empty line immediately preceding the import
block so imports start directly at the top of the file with no leading blank
lines.

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java:
- Line 3: Remove the unnecessary blank line before the import statements in the
ManageGisuPort file so imports start immediately after the package/declaration;
locate the interface ManageGisuPort and ensure there are no extra empty lines
above its import block (keep only a single blank line where project style
requires, or none if package is at top).

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java:
- Line 3: Remove the unnecessary blank line appearing before the import
statements in the ManageSchoolPort.java file; open the ManageSchoolPort
interface and delete the empty line(s) directly above the first import so
imports start immediately at the top of the file.
- Around line 6-11: The ManageSchoolPort interface currently combines save and
delete (methods save(School) and delete(School)), which violates the Interface
Segregation Principle; split it into smaller Port Out interfaces by creating
SaveSchoolPort exposing save(School) and DeleteSchoolPort exposing
delete(School) (or keep a single SaveSchoolPort that also includes
delete(School) if you intentionally allow a Manage pattern for simple CRUD),
update all usages to depend on the new interface(s) (replace references to
ManageSchoolPort with SaveSchoolPort and/or DeleteSchoolPort), and document the
chosen approach and rationale in CLAUDE.md so future contributors know whether
Manage vs. segregated ports is the project standard.

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java:
- Around line 6-11: The ManageStudyGroupMemberPort interface violates ISP and
naming conventions by combining save and delete; split it into two ports: create
a SaveStudyGroupMemberPort with StudyGroupMember save(StudyGroupMember) and a
DeleteStudyGroupMemberPort with void delete(StudyGroupMember), update any
classes currently implementing or injecting ManageStudyGroupMemberPort to
implement/inject the appropriate new interfaces (e.g., repositories, adapters,
use-cases), and replace references to ManageStudyGroupMemberPort with the
corresponding SaveStudyGroupMemberPort or DeleteStudyGroupMemberPort while
keeping the existing method signatures and package structure.

In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java:
- Around line 6-10: ManageStudyGroupPort combines save and delete which violates
ISP and naming conventions consistent with Load{Domain}Port/Save{Domain}Port
patterns; split it into two ports by renaming/refactoring ManageStudyGroupPort
into SaveStudyGroupPort with the signature StudyGroup save(StudyGroup
studyGroup) and creating a new DeleteStudyGroupPort interface with void
delete(StudyGroup studyGroup), then update all implementations and injection
points that reference ManageStudyGroupPort to depend on the specific
SaveStudyGroupPort and/or DeleteStudyGroupPort (similar to how ManageChapterPort
was handled).
- Around line 2-3: Remove the unnecessary blank line after the package
declaration in the ManageStudyGroupPort.java file to improve readability; open
the file containing the package declaration and the public interface
ManageStudyGroupPort and delete the extra empty line immediately following the
package statement so the package declaration is directly followed by the import
or interface declaration.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa6c4f8 and c90b00d.

📒 Files selected for processing (8)
  • CLAUDE.md
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
🧰 Additional context used
📓 Path-based instructions (2)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
🧠 Learnings (6)
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • CLAUDE.md
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Port Out interface method names must be technology-neutral (e.g., findById, save) not database-specific

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageGisuPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterSchoolPort.java
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
  • CLAUDE.md
  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/out/**Port.java : Load Port methods must use Optional for single object returns and avoid returning Response DTOs

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/out/persistence/**PersistenceAdapter.java : Persistence adapters (in adapter/out/persistence/) must implement Load{Domain}Port and Save{Domain}Port interfaces and delegate to JPA repositories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
🔇 Additional comments (1)
src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java (1)

2-3: 불필요한 빈 줄 제거

코드 가독성을 위해 불필요한 빈 줄을 제거해주세요. 이전 리뷰에서도 동일한 의견이 제시되었습니다.

🔎 제안하는 수정
 package com.umc.product.organization.application.port.out.command;
-

 import com.umc.product.organization.domain.StudyGroupMember;
 
 public interface ManageStudyGroupMemberPort {
-
     StudyGroupMember save(StudyGroupMember studyGroupMember);
-
     void delete(StudyGroupMember studyGroupMember);

Also applies to: 7-7, 9-9

Likely an incorrect or invalid review comment.

Comment on lines +173 to +181
### Manage 통합 옵션

CUD(Create, Update, Delete)를 하나의 인터페이스로 통합하고 싶다면 `Manage` 접두사를 사용할 수 있습니다.

| 대상 | 개별형 | 통합형 |
|------|--------|--------|
| **UseCase** | `CreateSchoolUseCase`, `UpdateSchoolUseCase`, `DeleteSchoolUseCase` | `ManageSchoolUseCase` |
| **Port** | `SaveSchoolPort` | `ManageSchoolPort` |

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

문서와 실제 코드 간 명명 규칙 불일치를 수정하세요.

표의 Port 명명 예시가 실제 구현된 코드와 일치하지 않습니다:

  • 표에는 SchoolManagePort로 표기
  • 실제 코드는 ManageSchoolPort, ManageChapterSchoolPort, ManageGisuPort 등으로 구현

실제 코드에 맞춰 표를 수정하거나, 명시적으로 프로젝트 전체에서 사용하는 규칙을 명확히 해주세요.

🔎 수정 제안
 | 대상 | 개별형 | 통합형 |
 |------|--------|--------|
 | **UseCase** | `CreateSchoolUseCase`, `UpdateSchoolUseCase`, `DeleteSchoolUseCase` | `ManageSchoolUseCase` |
-| **Port** | `SaveSchoolPort` | `SchoolManagePort` |
+| **Port** | `SaveSchoolPort` | `ManageSchoolPort` |

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @CLAUDE.md around lines 173-181, The Port naming in the documentation table
conflicts with the code: the table lists SchoolManagePort but the code uses
ManageSchoolPort, ManageChapterSchoolPort, ManageGisuPort; update the table to
use the actual implemented names (e.g., ManageSchoolPort) or add a short note
declaring the project's chosen convention ("Manage" prefix before entity, e.g.,
ManageSchoolPort) and rename the examples accordingly so docs and code match
(ensure references to ManageSchoolPort, ManageChapterSchoolPort, ManageGisuPort
are used consistently).

Comment on lines +6 to +13
public interface ManageCentralOrganizationPort {

Optional<CentralOrganization> findById(Long id);

CentralOrganization save(CentralOrganization organization);

void delete(CentralOrganization organization);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*Port.java" -path "*/application/port/out/*" | head -20

Repository: UMC-PRODUCT/umc-product-server

Length of output: 1350


🏁 Script executed:

# Check the structure and content of other Manage*Port files
find . -type f -name "Manage*Port.java" -path "*/application/port/out/*"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 778


🏁 Script executed:

# Also check for Load*Port files to see if Load/Save separation is used
find . -type f -name "Load*Port.java" -path "*/application/port/out/*"
find . -type f -name "Save*Port.java" -path "*/application/port/out/*"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 645


🏁 Script executed:

# Check the content of ManageCentralOrganizationPort to confirm
cat -n src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 562


쿼리와 명령 관심사 분리가 필요합니다.

ManageCentralOrganizationPort가 조회(findById)와 명령(save, delete) 작업을 함께 포함하고 있습니다. 다른 모든 Manage 포트(ManageSchoolPort, ManageGisuPort, ManageChapterPort 등)는 명령 작업만 포함하고, 조회는 별도의 Load 포트에서 담당하는 구조를 따르고 있습니다.

현재 구조 문제:

  • 같은 패키지의 다른 포트들과 일관성 부족
  • 패키지명("command")과 인터페이스 내용 불일치
  • ISP 위반: 저장만 필요한 클라이언트도 조회 메서드에 의존

다음과 같이 분리하세요:

수정안
// application/port/out/query/LoadCentralOrganizationPort.java
public interface LoadCentralOrganizationPort {
    Optional<CentralOrganization> findById(Long id);
}

// application/port/out/command/ManageCentralOrganizationPort.java (현재 파일)
public interface ManageCentralOrganizationPort {
    CentralOrganization save(CentralOrganization organization);
    void delete(CentralOrganization organization);
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
around lines 6-13, The ManageCentralOrganizationPort currently mixes query and
command concerns: remove the findById method from the
ManageCentralOrganizationPort interface (leaving only save(CentralOrganization)
and delete(CentralOrganization)), create a new LoadCentralOrganizationPort
interface that declares Optional<CentralOrganization> findById(Long id), and
update any implementations, injection points, and usages to depend on
LoadCentralOrganizationPort for reads and ManageCentralOrganizationPort for
writes so the package/name and ISP are consistent.

Comment on lines +14 to +15

}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

불필요한 빈 줄을 제거하세요.

인터페이스 끝에 불필요한 빈 줄들이 있습니다.

🔎 수정 제안
     void delete(CentralOrganization organization);
-
-
 }
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageCentralOrganizationPort.java
around lines 14-15, Remove the unnecessary blank lines at the end of the
interface declaration for ManageCentralOrganizationPort so the file ends
immediately after the closing brace; ensure there is only a single newline after
the closing '}' of the ManageCentralOrganizationPort interface and no extra
empty lines following it.

Comment on lines +2 to +3


Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

불필요한 빈 줄 제거

코드 가독성을 위해 불필요한 빈 줄을 제거해주세요.

🔎 제안하는 수정
 package com.umc.product.organization.application.port.out.command;
-

 import com.umc.product.organization.domain.Chapter;
 
 public interface ManageChapterPort {
-

     Chapter save(Chapter chapter);

Also applies to: 7-8

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
around lines 2-3, Remove the unnecessary blank lines in the
ManageChapterPort.java file to improve readability; specifically delete the
extra empty lines at the top of the file and the blank lines around lines 7-8
(inside the ManageChapterPort interface) so the interface declaration and its
members are compact and do not contain spurious blank lines.

Comment on lines +6 to +12
public interface ManageChapterPort {


Chapter save(Chapter chapter);

void delete(Chapter chapter);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인터페이스 분리 원칙(ISP) 위반 및 명명 규칙 불일치

현재 ManageChapterPortsavedelete 메서드를 하나의 인터페이스에 결합하고 있습니다. 이는 다음 문제를 발생시킵니다:

  1. ISP 위반: 저장과 삭제 작업을 단일 인터페이스에 결합하면, 한 가지 작업만 필요한 클라이언트도 불필요한 의존성을 갖게 됩니다.
  2. 명명 규칙 불일치: 프로젝트의 아키텍처 가이드라인에 따르면 Load{Domain}Port, Save{Domain}Port 패턴을 따라야 합니다.

SaveChapterPort (save 메서드), DeleteChapterPort (delete 메서드)로 분리하거나, 최소한 Save{Domain}Port 명명 규칙을 따르는 것을 권장합니다.

Based on learnings, Port Out 인터페이스는 ISP를 준수하여 Load/Save 포트로 분리되어야 합니다.

🔎 제안하는 인터페이스 분리
-public interface ManageChapterPort {
-
-
-    Chapter save(Chapter chapter);
-
-    void delete(Chapter chapter);
-}
+public interface SaveChapterPort {
+    Chapter save(Chapter chapter);
+}

별도 파일로 삭제 포트 생성:

public interface DeleteChapterPort {
    void delete(Chapter chapter);
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageChapterPort.java
around lines 6-12, ManageChapterPort currently combines save and delete
operations violating ISP and naming conventions; split it into two ports by
extracting the save(Chapter chapter) method into a SaveChapterPort interface and
the delete(Chapter chapter) method into a DeleteChapterPort interface (or at
minimum rename to SaveChapterPort/DeleteChapterPort to follow
Save{Domain}Port/Delete{Domain}Port patterns), update any classes referencing
ManageChapterPort to depend on the specific SaveChapterPort or
DeleteChapterPort, and remove the save/delete methods from the original
ManageChapterPort.

@@ -0,0 +1,11 @@
package com.umc.product.organization.application.port.out.command;


Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

불필요한 빈 줄을 제거하세요.

import 문 앞에 불필요한 빈 줄이 있습니다.

🔎 수정 제안
 package com.umc.product.organization.application.port.out.command;
-

 import com.umc.product.organization.domain.School;
📝 Committable suggestion

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

Suggested change
package com.umc.product.organization.application.port.out.command;
import com.umc.product.organization.domain.School;
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
around line 3, Remove the unnecessary blank line appearing before the import
statements in the ManageSchoolPort.java file; open the ManageSchoolPort
interface and delete the empty line(s) directly above the first import so
imports start immediately at the top of the file.

Comment on lines +6 to +11
public interface ManageSchoolPort {

School save(School school);

void delete(School school);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

인터페이스 분리 원칙(ISP) 위반을 검토하세요.

ManageSchoolPortsavedelete 두 가지 쓰기 작업을 단일 인터페이스에 통합하고 있습니다. 프로젝트의 기존 학습 규칙에 따르면 Port Out 인터페이스는 Load{Domain}PortSave{Domain}Port로 분리되어야 합니다.

통합 접근 방식의 장단점:

  • 장점: 간단한 CRUD 작업에 대해 인터페이스 수가 줄어들어 관리가 용이
  • 단점: ISP 위반으로 인해 save만 필요한 클라이언트도 delete에 의존하게 됨

PR 목표에서 이 설계에 대한 피드백을 요청하셨는데, 다음을 권장합니다:

  1. 단순 CRUD 도메인의 경우 Manage 패턴 허용
  2. 복잡한 도메인이나 다양한 클라이언트가 있는 경우 Load/Save 분리 유지
  3. 선택한 접근 방식을 CLAUDE.md에 명확히 문서화

Based on learnings, Port Out interfaces should follow Interface Segregation Principle.

🔎 ISP를 준수하는 대안
// Option 1: 기존 패턴 유지 (ISP 준수)
public interface SaveSchoolPort {
    School save(School school);
    void delete(School school);
}

// Option 2: 더 세분화 (완전한 ISP)
public interface SaveSchoolPort {
    School save(School school);
}

public interface DeleteSchoolPort {
    void delete(School school);
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageSchoolPort.java
around lines 6-11, The ManageSchoolPort interface currently combines save and
delete (methods save(School) and delete(School)), which violates the Interface
Segregation Principle; split it into smaller Port Out interfaces by creating
SaveSchoolPort exposing save(School) and DeleteSchoolPort exposing
delete(School) (or keep a single SaveSchoolPort that also includes
delete(School) if you intentionally allow a Manage pattern for simple CRUD),
update all usages to depend on the new interface(s) (replace references to
ManageSchoolPort with SaveSchoolPort and/or DeleteSchoolPort), and document the
chosen approach and rationale in CLAUDE.md so future contributors know whether
Manage vs. segregated ports is the project standard.

Comment on lines +6 to +11
public interface ManageStudyGroupMemberPort {

StudyGroupMember save(StudyGroupMember studyGroupMember);

void delete(StudyGroupMember studyGroupMember);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인터페이스 분리 원칙(ISP) 위반 및 명명 규칙 불일치

ManageStudyGroupMemberPort도 동일한 아키텍처 원칙 위반이 있습니다:

  1. ISP 위반: savedelete를 단일 인터페이스에 결합
  2. 명명 규칙 불일치: Load{Domain}Port, Save{Domain}Port 패턴을 따르지 않음

SaveStudyGroupMemberPortDeleteStudyGroupMemberPort로 분리하는 것을 권장합니다.

Based on learnings, Port Out 인터페이스는 ISP를 준수하여 Load/Save 포트로 분리되어야 합니다.

🔎 제안하는 인터페이스 분리
-public interface ManageStudyGroupMemberPort {
-
-    StudyGroupMember save(StudyGroupMember studyGroupMember);
-
-    void delete(StudyGroupMember studyGroupMember);
-}
+public interface SaveStudyGroupMemberPort {
+    StudyGroupMember save(StudyGroupMember studyGroupMember);
+}

별도 파일로 삭제 포트 생성:

public interface DeleteStudyGroupMemberPort {
    void delete(StudyGroupMember studyGroupMember);
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupMemberPort.java
around lines 6-11, The ManageStudyGroupMemberPort interface violates ISP and
naming conventions by combining save and delete; split it into two ports: create
a SaveStudyGroupMemberPort with StudyGroupMember save(StudyGroupMember) and a
DeleteStudyGroupMemberPort with void delete(StudyGroupMember), update any
classes currently implementing or injecting ManageStudyGroupMemberPort to
implement/inject the appropriate new interfaces (e.g., repositories, adapters,
use-cases), and replace references to ManageStudyGroupMemberPort with the
corresponding SaveStudyGroupMemberPort or DeleteStudyGroupMemberPort while
keeping the existing method signatures and package structure.

Comment on lines +2 to +3


Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

불필요한 빈 줄 제거

코드 가독성을 위해 패키지 선언 후 불필요한 빈 줄을 제거해주세요.

🔎 제안하는 수정
 package com.umc.product.organization.application.port.out.command;
-

 import com.umc.product.organization.domain.StudyGroup;
📝 Committable suggestion

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

Suggested change
package com.umc.product.organization.application.port.out.command;
import com.umc.product.organization.domain.StudyGroup;
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
around lines 2-3, Remove the unnecessary blank line after the package
declaration in the ManageStudyGroupPort.java file to improve readability; open
the file containing the package declaration and the public interface
ManageStudyGroupPort and delete the extra empty line immediately following the
package statement so the package declaration is directly followed by the import
or interface declaration.

Comment on lines +6 to +10
public interface ManageStudyGroupPort {

StudyGroup save(StudyGroup studyGroup);
void delete(StudyGroup studyGroup);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

인터페이스 분리 원칙(ISP) 위반 및 명명 규칙 불일치

ManageStudyGroupPortManageChapterPort와 동일한 문제를 가지고 있습니다:

  1. ISP 위반: savedelete를 단일 인터페이스에 결합
  2. 명명 규칙 불일치: Load{Domain}Port, Save{Domain}Port 패턴을 따르지 않음

SaveStudyGroupPortDeleteStudyGroupPort로 분리하는 것을 권장합니다.

Based on learnings, Port Out 인터페이스는 ISP를 준수하여 분리되어야 합니다.

🔎 제안하는 인터페이스 분리
-public interface ManageStudyGroupPort {
-
-    StudyGroup save(StudyGroup studyGroup);
-    void delete(StudyGroup studyGroup);
-}
+public interface SaveStudyGroupPort {
+    StudyGroup save(StudyGroup studyGroup);
+}

별도 파일로 삭제 포트 생성:

public interface DeleteStudyGroupPort {
    void delete(StudyGroup studyGroup);
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/application/port/out/command/ManageStudyGroupPort.java
around lines 6-10, ManageStudyGroupPort combines save and delete which violates
ISP and naming conventions consistent with Load{Domain}Port/Save{Domain}Port
patterns; split it into two ports by renaming/refactoring ManageStudyGroupPort
into SaveStudyGroupPort with the signature StudyGroup save(StudyGroup
studyGroup) and creating a new DeleteStudyGroupPort interface with void
delete(StudyGroup studyGroup), then update all implementations and injection
points that reference ManageStudyGroupPort to depend on the specific
SaveStudyGroupPort and/or DeleteStudyGroupPort (similar to how ManageChapterPort
was handled).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Fix all issues with AI agents
In @src/main/java/com/umc/product/global/response/CursorPageable.java:
- Line 9: CursorPageable currently defines size(), which is redundant with
content().size(); either remove size() from the CursorPageable interface and
update all callers/implementations to use content().size(), or if you intended
to expose the requested page size, rename size() to a clearer name such as
pageSize() or limit() and update all implementations and usages accordingly
(ensure method signature changes propagate to any classes implementing
CursorPageable and any code calling size()).
- Around line 5-10: Add Javadoc to the public interface CursorPageable and to
each method to document behavior and contracts: describe that CursorPageable<T>
represents a single cursor-based page, content() returns the list of items on
the current page, nextCursor() returns the cursor value for fetching the next
page and may be null when there is no next page, hasNext() returns whether a
next page exists, and size() returns the number of items in the current page
(not the requested page size); include nullability and
thread-safety/immutability notes if applicable and any expectations about
callers (e.g., whether content() may be empty but non-null).
- Line 1: The package for CursorPageable violates the hexagonal architecture
guideline: move the CursorPageable type from package global.response to a
hexagonal-compliant package such as common.dto.response if it is a reusable
pagination DTO across domains, or to an appropriate application.port.out package
if it represents an outbound port response contract; update the package
declaration and any imports/usages referencing CursorPageable throughout the
codebase to the new package name to ensure compilation and consistent layering.

In @src/main/java/com/umc/product/global/response/OffsetPageable.java:
- Line 1: The OffsetPageable class currently lives in the package
global.response which violates our hexagonal architecture; move the
OffsetPageable type to a package that reflects its cross-cutting role — e.g.,
common.dto.response if it is a shared DTO or application.port.out if it
represents an outbound port response contract — update its package declaration
and any imports/usages to the new package, and ensure module boundaries and
package names follow domain/, application/{port/in,port/out,service}/,
adapter/{in,out}/ conventions.
- Around line 5-13: Add Javadoc to the public interface OffsetPageable<T>
documenting the contract for each method: describe content() as the list of
items on the current page, page() as the current zero-based (or one-based—pick
and state) page offset, size() explicitly as either the requested page limit or
the actual number of items returned on this page (state which you choose),
totalElements() as the overall element count, totalPages() as the total number
of pages computed from totalElements and the page size, and
hasNext()/hasPrevious() as booleans indicating whether a subsequent or preceding
page exists; update the Javadoc on content(), page(), size(), totalElements(),
totalPages(), hasNext(), and hasPrevious() to include these details and any
assumptions (e.g., indexing convention, nullable/immutable returns).
- Line 8: The OffsetPageable.size() method name is ambiguous about whether it
returns the actual content count or the requested page limit; update
OffsetPageable by renaming size() to a clearer name (e.g., pageSize() or
limit()) and update all usages and implementations accordingly, or alternatively
add a Javadoc on size() in the OffsetPageable interface that explicitly states
it returns the requested page size (limit) to remove ambiguity; ensure method
signature, callers, and any implementing classes (and tests) are updated to
reflect the chosen name/description.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java:
- Around line 18-26: The createSchool method in SchoolController lacks request
validation, OpenAPI docs, and the required ApiResponse wrapper: add @Valid to
the @RequestBody parameter (CreateSchoolRequest) so DTO constraints are
enforced, annotate the method with an appropriate @Operation summary/description
for OpenAPI, change the signature to return the standard ApiResponse<T> (or
ResponseEntity<ApiResponse<T>>) and wrap the result of
manageSchoolUseCase.register(...) in an ApiResponse (and return 201 Created if
appropriate); ensure you reference CreateSchoolRequest.toCommand() and
ManageSchoolUseCase.register(...) when constructing the wrapped response.
- Around line 11-14: SchoolController 클래스에 OpenAPI 문서화를 위해 클래스 레벨에 @Tag(name =
"학교", description = "...") 어노테이션을 추가하고, 각 public API 엔드포인트 메서드(예:
SchoolController 내의 모든 public 메서드)에 @Operation(summary = "...", description =
"...")를 추가하여 Swagger에 노출되도록 수정하세요; 필요하면 컨트롤러의 생성자나 메서드 시그니처는 건드리지 말고 import로
springfox 또는 springdoc의 올바른 @Tag/@Operation을 사용하도록 조정하세요.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java:
- Around line 9-14: The interface ManageSchoolUseCase contains commented-out
method signatures (update, delete, deleteAll); either remove these dead comments
or mark them clearly as planned work by replacing them with TODO comments
referencing a ticket/issue ID and a short rationale, or open a tracking issue
and add a one-line TODO above the commented methods; specifically address the
commented symbols update(UpdateSchoolCommand), delete(Long), and
deleteAll(List<Long>) in ManageSchoolUseCase so the intent is explicit and the
file has no lingering commented API.

In
@src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java:
- Line 25: Remove the debug print in the test: delete the
System.out.println(objectMapper.writeValueAsString(request)); call from
SchoolControllerTest (the test method that builds "request") so no debug stdout
remains; ensure no other System.out.println or similar debug prints exist in the
SchoolControllerTest class (replace with proper assertions or logger if needed).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65d6fc1 and 7fb7ec2.

📒 Files selected for processing (14)
  • src/docs/asciidoc/api/school/school.adoc
  • src/docs/asciidoc/index.adoc
  • src/main/java/com/umc/product/UmcProductApplication.java
  • src/main/java/com/umc/product/global/config/JpaConfig.java
  • src/main/java/com/umc/product/global/exception/constant/Domain.java
  • src/main/java/com/umc/product/global/response/CursorPageable.java
  • src/main/java/com/umc/product/global/response/OffsetPageable.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/test/java/com/umc/product/UmcProductApplicationTests.java
  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
💤 Files with no reviewable changes (2)
  • src/test/java/com/umc/product/UmcProductApplicationTests.java
  • src/main/java/com/umc/product/UmcProductApplication.java
🧰 Additional context used
📓 Path-based instructions (10)
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/docs/asciidoc/api/school/school.adoc
  • src/docs/asciidoc/index.adoc
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/global/response/CursorPageable.java
  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
  • src/main/java/com/umc/product/global/config/JpaConfig.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/global/response/OffsetPageable.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/global/exception/constant/Domain.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/global/response/CursorPageable.java
  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
  • src/main/java/com/umc/product/global/config/JpaConfig.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/global/response/OffsetPageable.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/global/exception/constant/Domain.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
**/*adapter/in/web/**Controller.java

📄 CodeRabbit inference engine (CLAUDE.md)

**/*adapter/in/web/**Controller.java: Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces
Controller methods must use @RequestMapping with API version path (/api/v1/{domain}), include @operation and @tag for OpenAPI/Swagger documentation
All API responses must follow the standard ApiResponse format with success boolean, data, and error fields
All public API endpoints must include OpenAPI/Swagger documentation with @tag and @operation annotations

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
**/*Test.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*Test.java: Test methods must use descriptive Korean names following {behavior}{condition}{result} pattern for clarity
Test methods must follow Given-When-Then structure with clear setup, execution, and assertion sections

Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Files:

  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
**/*global/config/**Config.java

📄 CodeRabbit inference engine (CLAUDE.md)

Spring configuration files must be in global/config/ package and use @configuration annotation

Files:

  • src/main/java/com/umc/product/global/config/JpaConfig.java
**/*adapter/in/web/dto/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
**/*adapter/in/web/dto/request/**Request.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request DTOs must include @Valid, @NotNull, and @notblank validation annotations; include toCommand() conversion method to command record

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
**/*global/exception/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Exception handling must use BusinessException, ErrorCode enums, and GlobalExceptionHandler in global/exception/ package

Files:

  • src/main/java/com/umc/product/global/exception/constant/Domain.java
**/*application/port/in/**/*{Command,Query}.java

📄 CodeRabbit inference engine (CLAUDE.md)

Command and Query records should be immutable with @NotNull validation and include conversion method toCommand() or toEntity()

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
🧠 Learnings (19)
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller methods must use RequestMapping with API version path (/api/v1/{domain}), include Operation and Tag for OpenAPI/Swagger documentation

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : REST API endpoints must follow naming conventions: plural forms for resource names and kebab-case for multi-word paths

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Query methods should implement pagination using Pageable parameter instead of returning entire result sets

Applied to files:

  • src/main/java/com/umc/product/global/response/CursorPageable.java
  • src/main/java/com/umc/product/global/response/OffsetPageable.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/config/**Config.java : Spring configuration files must be in global/config/ package and use Configuration annotation

Applied to files:

  • src/main/java/com/umc/product/global/config/JpaConfig.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : Controller classes must only depend on UseCase interfaces, never directly on Repository or Service implementations

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*domain/*{Status,Type}.java : Enum naming convention: {Domain}{Status|Type} (e.g., ChallengerStatus, ScheduleType)

Applied to files:

  • src/main/java/com/umc/product/global/exception/constant/Domain.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/domain/**/*{Status,Type,State,Kind}.java : Enum classes should follow {Domain}{Type} naming pattern (e.g., ChallengerStatus.java)

Applied to files:

  • src/main/java/com/umc/product/global/exception/constant/Domain.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*{application,adapter}/**Test.java : Unit tests must use ExtendWith(MockitoExtension.class), mock all Port/UseCase dependencies with Mock, and use BDD-style given/when/then structure

Applied to files:

  • src/test/java/com/umc/product/support/DocumentationTest.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*global/security/**/*.java : Security configuration must use JWT (io.jsonwebtoken 0.12.5) with JwtTokenProvider, JwtAuthenticationFilter in global/security/ package; use Public and CurrentUser annotations for method security

Applied to files:

  • src/test/java/com/umc/product/support/DocumentationTest.java
🔇 Additional comments (5)
src/main/java/com/umc/product/global/config/JpaConfig.java (1)

6-9: JPA Auditing 설정 분리가 적절하게 구현되었습니다.

Application 클래스에서 JPA Auditing 설정을 별도의 Config 클래스로 분리한 것은 관심사의 분리(Separation of Concerns) 측면에서 좋은 접근입니다. 코딩 가이드라인에 따라 global/config/ 패키지에 @Configuration 어노테이션을 사용한 것도 적절합니다.

BaseEntity에서 @CreatedDate@LastModifiedDate만 사용하고 있으므로, 현재 JpaConfig 구현으로 충분합니다. AuditorAware 빈은 @CreatedBy 또는 @LastModifiedBy 어노테이션을 사용할 때만 필요합니다.

Likely an incorrect or invalid review comment.

src/main/java/com/umc/product/global/exception/constant/Domain.java (1)

11-12: LGTM! 도메인 상수 추가가 적절합니다.

MEMBER와 CHALLENGER 도메인 상수가 명확하게 정의되었으며, 비즈니스 예외 처리를 위한 도메인 분류 목적에 부합합니다.

src/docs/asciidoc/index.adoc (1)

12-15: LGTM! API 문서 구조가 올바르게 추가되었습니다.

School API 섹션이 적절한 앵커와 include 지시자로 문서화되었습니다.

src/docs/asciidoc/api/school/school.adoc (1)

1-4: LGTM! REST Docs 문서가 올바르게 구성되었습니다.

총괄 학교 등록 API에 대한 문서 구조가 적절하며, REST Docs operation 참조가 올바르게 설정되었습니다.

src/test/java/com/umc/product/support/DocumentationTest.java (1)

17-19: LGTM! 테스트 인프라가 잘 구성되었습니다.

SchoolController 테스트를 위한 Mock 설정이 적절하게 추가되었습니다. @MockitoBean을 통한 의존성 주입과 @WebMvcTest 설정이 격리된 컨트롤러 테스트를 위한 best practice를 잘 따르고 있습니다.

Also applies to: 37-42

@@ -0,0 +1,10 @@
package com.umc.product.global.response;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

패키지 구조가 헥사고날 아키텍처 가이드라인을 따르지 않습니다.

코딩 가이드라인에 따르면 Java 파일은 헥사고날 아키텍처를 따라 domain/, application/{port/in, port/out, service}/, adapter/{in, out}/ 디렉토리 구조를 가져야 합니다. 현재 global.response 패키지는 이 구조와 맞지 않습니다.

페이지네이션 인터페이스가 여러 도메인에서 공통으로 사용된다면, common.dto.response 또는 application.port.out (아웃바운드 포트 응답 계약) 패키지로 이동을 고려해주세요.

Based on coding guidelines, Java package structure should follow hexagonal architecture.

🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/global/response/CursorPageable.java at line 1,
The package for CursorPageable violates the hexagonal architecture guideline:
move the CursorPageable type from package global.response to a
hexagonal-compliant package such as common.dto.response if it is a reusable
pagination DTO across domains, or to an appropriate application.port.out package
if it represents an outbound port response contract; update the package
declaration and any imports/usages referencing CursorPageable throughout the
codebase to the new package name to ensure compilation and consistent layering.

Comment on lines 5 to 10
public interface CursorPageable<T> {
List<T> content();
Long nextCursor();
boolean hasNext();
int size();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

인터페이스에 Javadoc 문서화가 필요합니다.

public 인터페이스의 각 메서드에 대한 문서화가 누락되었습니다. 특히 다음 사항들을 명확히 해주세요:

  • content(): 현재 페이지의 컨텐츠 목록
  • nextCursor(): 다음 페이지 조회를 위한 커서 값 (null 가능 여부 명시 필요)
  • hasNext(): 다음 페이지 존재 여부
  • size(): 현재 페이지의 컨텐츠 개수인지, 요청한 페이지 크기인지 명확히 구분 필요
📝 Javadoc 추가 예시
+/**
+ * 커서 기반 페이지네이션 응답을 위한 인터페이스
+ *
+ * @param <T> 컨텐츠 타입
+ */
 public interface CursorPageable<T> {
+    /**
+     * 현재 페이지의 컨텐츠 목록을 반환합니다.
+     *
+     * @return 컨텐츠 목록
+     */
     List<T> content();
+    
+    /**
+     * 다음 페이지 조회를 위한 커서 값을 반환합니다.
+     *
+     * @return 다음 커서 값, 다음 페이지가 없으면 null
+     */
     Long nextCursor();
+    
+    /**
+     * 다음 페이지 존재 여부를 반환합니다.
+     *
+     * @return 다음 페이지가 있으면 true
+     */
     boolean hasNext();
+    
+    /**
+     * 현재 페이지의 컨텐츠 개수를 반환합니다.
+     *
+     * @return 컨텐츠 개수
+     */
     int size();
 }
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/global/response/CursorPageable.java around
lines 5 - 10, Add Javadoc to the public interface CursorPageable and to each
method to document behavior and contracts: describe that CursorPageable<T>
represents a single cursor-based page, content() returns the list of items on
the current page, nextCursor() returns the cursor value for fetching the next
page and may be null when there is no next page, hasNext() returns whether a
next page exists, and size() returns the number of items in the current page
(not the requested page size); include nullability and
thread-safety/immutability notes if applicable and any expectations about
callers (e.g., whether content() may be empty but non-null).

List<T> content();
Long nextCursor();
boolean hasNext();
int size();
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

size() 메서드의 필요성을 재검토해주세요.

size() 메서드는 content().size()로 대체 가능하여 중복된 것으로 보입니다. 커서 기반 페이지네이션에서는 일반적으로 컨텐츠 리스트 자체의 크기만으로 충분합니다.

만약 요청한 페이지 크기(limit)를 별도로 반환하려는 의도라면, 메서드명을 pageSize() 또는 limit()로 변경하여 의미를 명확히 하거나, 불필요하다면 제거를 고려해주세요.

🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/global/response/CursorPageable.java at line 9,
CursorPageable currently defines size(), which is redundant with
content().size(); either remove size() from the CursorPageable interface and
update all callers/implementations to use content().size(), or if you intended
to expose the requested page size, rename size() to a clearer name such as
pageSize() or limit() and update all implementations and usages accordingly
(ensure method signature changes propagate to any classes implementing
CursorPageable and any code calling size()).

@@ -0,0 +1,13 @@
package com.umc.product.global.response;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

패키지 구조가 헥사고날 아키텍처 가이드라인을 따르지 않습니다.

코딩 가이드라인에 따르면 Java 파일은 헥사고날 아키텍처를 따라 domain/, application/{port/in, port/out, service}/, adapter/{in, out}/ 디렉토리 구조를 가져야 합니다. 현재 global.response 패키지는 이 구조와 맞지 않습니다.

페이지네이션 인터페이스가 여러 도메인에서 공통으로 사용된다면, common.dto.response 또는 application.port.out (아웃바운드 포트 응답 계약) 패키지로 이동을 고려해주세요.

Based on coding guidelines, Java package structure should follow hexagonal architecture.

🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/global/response/OffsetPageable.java at line 1,
The OffsetPageable class currently lives in the package global.response which
violates our hexagonal architecture; move the OffsetPageable type to a package
that reflects its cross-cutting role — e.g., common.dto.response if it is a
shared DTO or application.port.out if it represents an outbound port response
contract — update its package declaration and any imports/usages to the new
package, and ensure module boundaries and package names follow domain/,
application/{port/in,port/out,service}/, adapter/{in,out}/ conventions.

Comment on lines 5 to 13
public interface OffsetPageable<T> {
List<T> content();
int page();
int size();
long totalElements();
int totalPages();
boolean hasNext();
boolean hasPrevious();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

인터페이스에 Javadoc 문서화가 필요합니다.

public 인터페이스의 각 메서드에 대한 문서화가 누락되었습니다. 특히 size() 메서드가 현재 페이지의 실제 컨텐츠 개수인지, 요청한 페이지 크기(limit)인지 명확히 해주세요.

📝 Javadoc 추가 예시
+/**
+ * 오프셋 기반 페이지네이션 응답을 위한 인터페이스
+ *
+ * @param <T> 컨텐츠 타입
+ */
 public interface OffsetPageable<T> {
+    /**
+     * 현재 페이지의 컨텐츠 목록을 반환합니다.
+     *
+     * @return 컨텐츠 목록
+     */
     List<T> content();
+    
+    /**
+     * 현재 페이지 번호를 반환합니다. (0-based 또는 1-based 명시 필요)
+     *
+     * @return 페이지 번호
+     */
     int page();
+    
+    /**
+     * 페이지당 요청한 크기를 반환합니다.
+     *
+     * @return 페이지 크기
+     */
     int size();
+    
+    /**
+     * 전체 요소의 개수를 반환합니다.
+     *
+     * @return 전체 요소 개수
+     */
     long totalElements();
+    
+    /**
+     * 전체 페이지 수를 반환합니다.
+     *
+     * @return 전체 페이지 수
+     */
     int totalPages();
+    
+    /**
+     * 다음 페이지 존재 여부를 반환합니다.
+     *
+     * @return 다음 페이지가 있으면 true
+     */
     boolean hasNext();
+    
+    /**
+     * 이전 페이지 존재 여부를 반환합니다.
+     *
+     * @return 이전 페이지가 있으면 true
+     */
     boolean hasPrevious();
 }
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/global/response/OffsetPageable.java around
lines 5 - 13, Add Javadoc to the public interface OffsetPageable<T> documenting
the contract for each method: describe content() as the list of items on the
current page, page() as the current zero-based (or one-based—pick and state)
page offset, size() explicitly as either the requested page limit or the actual
number of items returned on this page (state which you choose), totalElements()
as the overall element count, totalPages() as the total number of pages computed
from totalElements and the page size, and hasNext()/hasPrevious() as booleans
indicating whether a subsequent or preceding page exists; update the Javadoc on
content(), page(), size(), totalElements(), totalPages(), hasNext(), and
hasPrevious() to include these details and any assumptions (e.g., indexing
convention, nullable/immutable returns).

public interface OffsetPageable<T> {
List<T> content();
int page();
int size();
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

size() 메서드의 의미를 명확히 해주세요.

size() 메서드가 다음 중 어느 것을 의미하는지 불분명합니다:

  1. 현재 페이지의 실제 컨텐츠 개수 (content().size()와 동일)
  2. 요청한 페이지 크기 (limit)

일반적으로 오프셋 기반 페이지네이션에서는 요청한 페이지 크기를 의미하므로, 메서드명을 pageSize() 또는 limit()로 변경하여 명확히 하는 것을 권장합니다. 또는 Javadoc에 명확한 설명을 추가해주세요.

♻️ 메서드명 개선 제안
-    int size();
+    int pageSize(); // 또는 limit()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/global/response/OffsetPageable.java at line 8,
The OffsetPageable.size() method name is ambiguous about whether it returns the
actual content count or the requested page limit; update OffsetPageable by
renaming size() to a clearer name (e.g., pageSize() or limit()) and update all
usages and implementations accordingly, or alternatively add a Javadoc on size()
in the OffsetPageable interface that explicitly states it returns the requested
page size (limit) to remove ambiguity; ensure method signature, callers, and any
implementing classes (and tests) are updated to reflect the chosen
name/description.

Comment on lines +3 to +8
public record CreateSchoolCommand(
String schoolName,
String chapterId,
String remark
) {
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

이전 리뷰 지적사항이 아직 해결되지 않았습니다.

Command 레코드에 다음 필수 요소들이 여전히 누락되어 있습니다:

  1. 검증 어노테이션 누락: schoolNamechapterId@NotNull 또는 @NotBlank 검증이 없습니다.
  2. 변환 메서드 누락: Command DTO는 toEntity() 메서드를 포함해야 합니다.
  3. 타입 검증 필요: chapterIdString 타입으로 선언되어 있는데, 일반적으로 ID 필드는 Long 타입을 사용합니다. 의도된 설계인지 확인이 필요합니다.

As per coding guidelines, Command 레코드는 불변성과 함께 검증 어노테이션 및 변환 메서드를 반드시 포함해야 합니다.

🔍 chapterId 타입 검증 스크립트

다음 스크립트로 코드베이스 전체에서 chapterId 사용 패턴을 확인하세요:

#!/bin/bash
# chapterId가 다른 곳에서 어떤 타입으로 선언되었는지 확인

# Chapter 엔티티에서 ID 필드 타입 확인
echo "=== Chapter 엔티티의 ID 필드 타입 확인 ==="
ast-grep --pattern $'class Chapter {
  $$$
  @Id
  $$$ $ID_FIELD;
  $$$
}'

# chapterId를 Long으로 사용하는 곳 검색
echo -e "\n=== chapterId를 Long으로 사용하는 코드 ==="
rg -nP --type=java -C2 '\bLong\s+chapterId\b'

# chapterId를 String으로 사용하는 곳 검색
echo -e "\n=== chapterId를 String으로 사용하는 코드 ==="
rg -nP --type=java -C2 '\bString\s+chapterId\b'
🐛 수정 제안 (chapterId를 Long으로 가정)
 package com.umc.product.organization.application.port.in.command.dto;
 
+import com.umc.product.organization.domain.School;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
 public record CreateSchoolCommand(
+        @NotBlank(message = "학교명은 필수입니다")
         String schoolName,
-        String chapterId,
+        @NotNull(message = "지부 ID는 필수입니다")
+        Long chapterId,
         String remark
 ) {
+    public School toEntity() {
+        return School.builder()
+                .schoolName(schoolName)
+                .chapterId(chapterId)
+                .remark(remark)
+                .build();
+    }
 }

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java:
- Around line 12-15: Add the OpenAPI @Tag annotation to the SchoolController
class: annotate the public controller class SchoolController with @Tag providing
a tag name (e.g., "Admin Schools") and an optional description to satisfy the
project's guideline that all public API endpoints include @Tag at the class
level; ensure the annotation is imported from
io.swagger.v3.oas.annotations.tags.Tag so the class-level metadata is available
for OpenAPI generation.

In
@src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java:
- Line 1: Test package does not mirror production controller package; change the
package declaration in SchoolControllerTest to the controller's package (e.g.,
the same package as the controller under adapter.in.web) and relocate the test
file into the matching directory so the test class SchoolControllerTest resides
in com.umc.product.organization.adapter.in.web (or the exact package of the
controller), ensuring package and directory structure match the production
controller.
- Around line 18-34: Test mixes Given/When/Then and lacks verification of
business call and response content; refactor the test to clearly separate Given
(build CreateSchoolRequest), When (perform mockMvc POST to
"/api/v1/admin/schools" with JSON payload via objectMapper), Then (assert status
is OK, assert response body fields via result.andExpect(jsonPath(...)) and
document with restDocsHandler.document). Additionally verify that the mocked
manageSchoolUseCase.register(...) was invoked with the expected parameters—use
Mockito.verify(manageSchoolUseCase).register(...) or an ArgumentCaptor to assert
the Register DTO contains schoolName, chapterId, remark from the
CreateSchoolRequest; keep restDocsHandler.document call in the Then block.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb7ec2 and 2eb2474.

📒 Files selected for processing (2)
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
🧰 Additional context used
📓 Path-based instructions (4)
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
**/*Test.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*Test.java: Test methods must use descriptive Korean names following {behavior}{condition}{result} pattern for clarity
Test methods must follow Given-When-Then structure with clear setup, execution, and assertion sections

Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Files:

  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
**/*adapter/in/web/**Controller.java

📄 CodeRabbit inference engine (CLAUDE.md)

**/*adapter/in/web/**Controller.java: Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces
Controller methods must use @RequestMapping with API version path (/api/v1/{domain}), include @operation and @tag for OpenAPI/Swagger documentation
All API responses must follow the standard ApiResponse format with success boolean, data, and error fields
All public API endpoints must include OpenAPI/Swagger documentation with @tag and @operation annotations

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
🧠 Learnings (7)
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*Test.java : Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Applied to files:

  • src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller methods must use RequestMapping with API version path (/api/v1/{domain}), include Operation and Tag for OpenAPI/Swagger documentation

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : REST API endpoints must follow naming conventions: plural forms for resource names and kebab-case for multi-word paths

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/**Controller.java : All public API endpoints must include OpenAPI/Swagger documentation with Tag and Operation annotations

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.568Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.568Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:22:39.472Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.472Z
Learning: Applies to **/adapter/in/**Controller.java : Controller methods must validate request bodies using Valid annotation and request parameter constraints

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java

@@ -0,0 +1,36 @@
package com.umc.product.organization.application.in;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

패키지 구조를 프로덕션 코드와 일치시켜주세요.

테스트 클래스의 패키지가 application.in으로 되어 있는데, 실제 컨트롤러는 adapter.in.web 패키지에 위치합니다. 테스트 패키지 구조는 프로덕션 코드의 패키지 구조를 반영해야 합니다.

📦 제안하는 수정안
-package com.umc.product.organization.application.in;
+package com.umc.product.organization.adapter.in.web;
📝 Committable suggestion

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

Suggested change
package com.umc.product.organization.application.in;
package com.umc.product.organization.adapter.in.web;
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
at line 1, Test package does not mirror production controller package; change
the package declaration in SchoolControllerTest to the controller's package
(e.g., the same package as the controller under adapter.in.web) and relocate the
test file into the matching directory so the test class SchoolControllerTest
resides in com.umc.product.organization.adapter.in.web (or the exact package of
the controller), ensuring package and directory structure match the production
controller.

Comment on lines 18 to 34
@Test
void 총괄_신규학교를_추가합니다() throws Exception {
// given when
CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("중앙대학교").chapterId("3")
.remark("중앙대는 멋집니다.").build();

// then
ResultActions result = mockMvc.perform(
post("/api/v1/admin/schools").content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON));

result.andExpect(status().isOk()).andDo(restDocsHandler.document(
requestFields(fieldWithPath("schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("chapterId").type(JsonFieldType.STRING).description("소속 지부 ID"),
fieldWithPath("remark").type(JsonFieldType.STRING).description("비고"))));

}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

테스트 구조 개선 및 mock 검증을 추가해주세요.

현재 테스트에 다음 문제점들이 있습니다:

  1. Given-When-Then 구조 불명확: 주석에 "given when"이 함께 표시되어 있어 구분이 명확하지 않습니다.
  2. Mock 검증 누락: manageSchoolUseCase.register()가 올바른 파라미터로 호출되었는지 검증하지 않습니다. HTTP 상태만 확인하면 실제 비즈니스 로직 호출 여부를 보장할 수 없습니다.
  3. 응답 검증 부족: 응답 본문의 구조나 내용을 검증하지 않습니다.

코딩 가이드라인에 따라 테스트는 Given-When-Then 구조를 명확히 구분하고, 핵심 로직의 검증이 충분해야 합니다.

✅ 제안하는 수정안
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+
     @Test
     void 총괄_신규학교를_추가합니다() throws Exception {
-        // given when
+        // given
         CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("중앙대학교").chapterId("3")
                 .remark("중앙대는 멋집니다.").build();
 
-        // then
+        // when
         ResultActions result = mockMvc.perform(
                 post("/api/v1/admin/schools").content(objectMapper.writeValueAsString(request))
                         .contentType(MediaType.APPLICATION_JSON));
 
+        // then
         result.andExpect(status().isOk()).andDo(restDocsHandler.document(
                 requestFields(fieldWithPath("schoolName").type(JsonFieldType.STRING).description("학교 이름"),
                         fieldWithPath("chapterId").type(JsonFieldType.STRING).description("소속 지부 ID"),
                         fieldWithPath("remark").type(JsonFieldType.STRING).description("비고"))));
+        
+        verify(manageSchoolUseCase).register(any());
 
     }
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/application/in/SchoolControllerTest.java
around lines 18 - 34, Test mixes Given/When/Then and lacks verification of
business call and response content; refactor the test to clearly separate Given
(build CreateSchoolRequest), When (perform mockMvc POST to
"/api/v1/admin/schools" with JSON payload via objectMapper), Then (assert status
is OK, assert response body fields via result.andExpect(jsonPath(...)) and
document with restDocsHandler.document). Additionally verify that the mocked
manageSchoolUseCase.register(...) was invoked with the expected parameters—use
Mockito.verify(manageSchoolUseCase).register(...) or an ArgumentCaptor to assert
the Register DTO contains schoolName, chapterId, remark from the
CreateSchoolRequest; keep restDocsHandler.document call in the Then block.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 23

🤖 Fix all issues with AI agents
In @src/docs/asciidoc/api/school/school.adoc:
- Line 12: The Asciidoc anchor ID `[[school-create]]` is duplicated for the
school delete operation; locate the duplicate anchor (the delete section anchor
currently set to `[[school-create]]`) and change it to a unique ID such as
`[[school-delete]]` so the create and delete anchors no longer collide and link
references remain unambiguous.

In
@src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java:
- Around line 12-15: The ChapterQueryController class is missing OpenAPI
annotations; annotate the controller class ChapterQueryController with @Tag
(e.g., @Tag(name = "Chapters", description = "Admin chapter APIs")) and add
@Operation annotations to each public endpoint method in this controller with
concise summaries; also add the necessary imports
(io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation) so the endpoints are included in
Swagger/OpenAPI documentation.
- Around line 19-23: The getAllChapter endpoint in ChapterQueryController is
missing OpenAPI documentation and is returning ChapterListResponse directly
instead of the standard ApiResponse<T>; add an @Operation annotation to the
getAllChapter method (e.g., annotate ChapterQueryController.getAllChapter) with
a brief summary/description and change the controller to wrap the result into
ApiResponse<ChapterListResponse> (or ApiResponse<List<ChapterInfo>> as per
existing types) before returning so responses contain success, data, and error
fields; ensure the method signature and return type use ApiResponse and update
any static factory or mapper (ChapterListResponse.from(...) usage) to produce
the inner data for the ApiResponse.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java:
- Around line 5-9: Update the UpdateSchoolRequest record so its components have
Bean Validation annotations: annotate schoolName and chapterId with appropriate
validation constraints (e.g., @NotBlank for non-empty strings, or
@NotNull/@NotBlank as your domain requires) and add validation to remark if it
must not be empty; ensure you import the javax.validation (or
jakarta.validation) annotations used so @Valid on the controller will trigger
validation for UpdateSchoolRequest.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java:
- Around line 16-19: The SchoolController class is missing OpenAPI annotations;
annotate the SchoolController class with @Tag (e.g., @Tag(name = "Schools",
description = "Admin school APIs")) and add @Operation annotations to each
public API handler method inside SchoolController (e.g., methods like
createSchool, getSchool, updateSchool, deleteSchool) providing a concise
summary/description; also add the necessary imports
(io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation) so the controller and its endpoints are
included in Swagger/OpenAPI docs.
- Around line 23-38: The controller methods createSchool, updateSchool, and
deleteSchool currently return void and lack @Operation annotations; change each
method signature to return ApiResponse<T> (use ApiResponse<Void> if no body or
ApiResponse<IdResponse>/appropriate DTO if you want to return created/updated
id), wrap the manageSchoolUseCase call result (or just return
ApiResponse.success(null) on success) into the ApiResponse structure, and add an
@Operation(...) annotation above each method describing the endpoint; update
createSchool, updateSchool, and deleteSchool to return ApiResponse and construct
success/error responses accordingly.
- Around line 30-33: The updateSchool method receives a @PathVariable schoolId
but never uses it; modify UpdateSchoolRequest.toCommand(...) to accept the
schoolId (or add an overloaded toCommand(Long schoolId)) and add a schoolId
field to UpdateSchoolCommand, then update manageSchoolUseCase.updateSchool(...)
call in SchoolController.updateSchool to pass the path variable into
request.toCommand(schoolId) so the use case knows which school to update; also
update any mapping/constructors and tests that build UpdateSchoolCommand
accordingly.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java:
- Around line 19-25: The controller method getSchoolDetail currently returns
SchoolDetailResponse directly; modify it to return
ApiResponse<SchoolDetailResponse> by wrapping the result with the standard
ApiResponse success wrapper (e.g., ApiResponse.success(...)), calling
SchoolDetailResponse.from(schoolInfo) and passing that into ApiResponse, and
update the method signature return type accordingly so responses include the
required success/data/error fields (refer to getSchoolDetail,
SchoolDetailResponse.from, and the ApiResponse class).
- Around line 12-15: The SchoolQueryController class is missing OpenAPI
annotations; add a class-level @Tag annotation (e.g., @Tag(name = "Schools",
description = "School management APIs")) on the SchoolQueryController and add
@Operation annotations to each public endpoint method in this class (include
summary and description attributes) using io.swagger.v3.oas.annotations.*,
ensuring imports are added and any existing controller methods (e.g., list/get
endpoints in SchoolQueryController) are annotated accordingly.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java:
- Around line 3-7: The UpdateSchoolCommand record is missing validation
annotations for required fields; add appropriate Bean Validation annotations
(e.g., @NotBlank on schoolName and @NotBlank or @NotNull on chapterId) to the
record components in UpdateSchoolCommand and import javax/ jakarta.validation
constraints, and add a conversion method (e.g., toCommand() or toEntity())
inside UpdateSchoolCommand that maps the validated components to the domain
command/entity type so callers can obtain an immutable, validated command
object.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java:
- Around line 14-15: Remove the commented-out method declaration in the
ManageSchoolUseCase interface: the commented line "//    void
deleteAll(List<Long> schoolIds);" should be deleted; if the behavior is
required, either un-comment and implement a concrete deleteAll(List<Long>
schoolIds) in the appropriate service/adapter or open a ticket to track adding
this API, but do not leave commented-out method signatures in
ManageSchoolUseCase.
- Around line 8-13: The interface ManageSchoolUseCase has inconsistent method
names: keep the short form and rename updateSchool and deleteSchool to update
and delete so all methods are register, update, delete; update the interface
method signatures (updateSchool -> update, deleteSchool -> delete) and adjust
any callers/implementations to the new method names (including references in
implementing classes and tests) to preserve consistency.

In
@src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java:
- Around line 12-19: The GetSchoolUseCase interface groups three distinct
retrieval responsibilities; split it into three single-responsibility
interfaces—GetAllSchoolsUseCase (exposing List<SchoolSummary> getAll(Long
gisuId)), GetSchoolListUseCase (exposing PageResult<DeletableSchoolSummary>
getList(SchoolDeleteSearchCondition condition, PageRequest pageRequest)), and
GetSchoolDetailUseCase (exposing SchoolInfo getSchoolDetail(Long schoolId));
update any implementing classes to implement the specific new interfaces, adjust
dependency injection/bean bindings and constructor parameters to depend on the
new interfaces, and refactor all call sites to use the corresponding new
interface for getAll, getList, and getSchoolDetail while keeping the existing
DTO/type names unchanged.

In
@src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java:
- Line 1: The SchoolService class is placed in the wrong package—move the
implementation out of the port package into the service package to follow
hexagonal structure; change the package declaration in the SchoolService class
from application.port.service.command to application.service.command, update any
import references and DI/bean registrations that reference
com.umc.product.organization.application.port.service.command.SchoolService to
the new package
(com.umc.product.organization.application.service.command.SchoolService), and
ensure any module descriptors or package-scoped tests are updated accordingly so
the service implementation resides under application.service.command while ports
remain under application.port.*.
- Around line 8-21: SchoolService currently has empty write methods and lacks
port dependencies and transaction management; inject outbound port interfaces
(e.g., LoadSchoolPort, SaveSchoolPort, DeleteSchoolPort) via constructor into
the SchoolService class and use those ports inside
register(CreateSchoolCommand), updateSchool(UpdateSchoolCommand) and
deleteSchool(Long) to perform persistence/lookup, annotate each of these three
methods with @Transactional, and add proper existence checks and exception
handling (e.g., throw a NotFound/DomainException when LoadSchoolPort cannot find
the requested school in updateSchool/deleteSchool) so the service depends on
ports not repositories and ensures transactional writes and clear error flows.

In @src/main/java/com/umc/product/organization/domain/School.java:
- Around line 60-64: In School.updateEmailDomain the conditional is inverted: it
updates this.emailDomain only when StringUtils.isEmpty(emailDomain) is true
(empty); change the check to ensure non-empty input is used (e.g., use
!StringUtils.isEmpty(emailDomain) or the same validation used in the
constructor) so emailDomain is only updated when a valid, non-empty value is
provided.
- Around line 54-58: The updateName method in School currently updates this.name
only when StringUtils.isEmpty(name) is true (i.e., when the input is empty);
invert the condition so the name is updated only when the incoming name is
non-empty (e.g., change the check to !StringUtils.isEmpty(name) or use the same
validation used at construction), ensuring you reference the updateName(String
name) method and StringUtils.isEmpty to locate and correct the logic.
- Around line 48-52: The method currently named updateLogoImageId incorrectly
takes a Long logoImageId but assigns logoImageUrl to itself and uses the
inverted empty check; change this to a correctly named updateLogoImageUrl method
that accepts a String logoImageUrl, check with if
(!StringUtils.isEmpty(logoImageUrl)) and then assign this.logoImageUrl =
logoImageUrl; alternatively, if the intent was to update an ID field,
rename/modify to updateLogoImageId(Long logoImageId), check for non-null
(logoImageId != null) and assign the proper this.logoImageId field instead.

In
@src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java:
- Around line 40-41: The REST Docs field type for ChapterInfo.id is incorrect:
in ChapterQueryControllerTest the fieldWithPath("result.chapters[].id") is
documented as JsonFieldType.STRING but ChapterInfo.id is a Long and test data
uses 1L/2L/3L; update the documentation to use JsonFieldType.NUMBER (or
NUMBER_TYPE constant) for that field so the generated docs match the actual Long
numeric type used by ChapterInfo and the test responses.

In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java:
- Around line 45-46: The test is using CreateSchoolRequest for the school-update
scenario; replace that with UpdateSchoolRequest in SchoolControllerTest (the
update test), construct an UpdateSchoolRequest with the appropriate fields
(e.g., schoolName, chapterId, remark) and call UpdateSchoolRequest.toCommand()
where the test currently uses CreateSchoolRequest.toCommand() or passes the
create DTO to the controller; also adjust any variable names and assertions to
reflect an update flow so the test exercises the update path using
UpdateSchoolRequest.
- Around line 34-36: The test documents chapterId as a STRING but Chapter.id and
School.id are Longs; either document intent or make types consistent: update
CreateSchoolRequest and CreateSchoolCommand to use Long chapterId (or add a
comment if string is intentional), change the test's field descriptor
fieldWithPath("chapterId").type(JsonFieldType.NUMBER).description(...) to
NUMBER, and update any related serialization/parsing and API docs to reflect
Long IDs so entity ID types are consistent across Chapter, School, and the
controller payloads.

In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java:
- Line 23: In SchoolQueryControllerTest update the comment typo: replace the
single-line comment "// give" with the correct "// given" in the test class
(SchoolQueryControllerTest) so the Arrange/Act/Assert section heading is
accurate.
- Around line 28-31: Remove the duplicate SchoolInfo construction and the unused
response variable: instantiate a single SchoolInfo (e.g., SchoolInfo schoolInfo
= new SchoolInfo(3L, "Ain 지부", "중앙대학교", 1L, "비고", createdAt, updatedAt)) and use
that instance to build any expected SchoolDetailResponse via
SchoolDetailResponse.from(schoolInfo) only if you need an expected DTO;
otherwise delete the unused response variable. Update the test assertions to
verify response body values using MockMvc jsonPath expectations (e.g., assert
chapterName, schoolName, schoolId and other fields from the
createdAt/updatedAt-backed object) so the test actually validates the returned
content.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2eb2474 and 5e09723.

📒 Files selected for processing (19)
  • src/docs/asciidoc/api/chapter/chapter.adoc
  • src/docs/asciidoc/api/school/school.adoc
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
🧰 Additional context used
📓 Path-based instructions (10)
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/docs/asciidoc/api/chapter/chapter.adoc
  • src/docs/asciidoc/api/school/school.adoc
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/domain/School.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java
**/*adapter/in/web/dto/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
**/*adapter/in/web/dto/request/**Request.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request DTOs must include @Valid, @NotNull, and @notblank validation annotations; include toCommand() conversion method to command record

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
**/domain/**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

State changes in Entity classes must be performed through domain methods, not via setters

Files:

  • src/main/java/com/umc/product/organization/domain/School.java
**/*Test.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*Test.java: Test methods must use descriptive Korean names following {behavior}{condition}{result} pattern for clarity
Test methods must follow Given-When-Then structure with clear setup, execution, and assertion sections

Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Files:

  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
**/*adapter/in/web/**Controller.java

📄 CodeRabbit inference engine (CLAUDE.md)

**/*adapter/in/web/**Controller.java: Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces
Controller methods must use @RequestMapping with API version path (/api/v1/{domain}), include @operation and @tag for OpenAPI/Swagger documentation
All API responses must follow the standard ApiResponse format with success boolean, data, and error fields
All public API endpoints must include OpenAPI/Swagger documentation with @tag and @operation annotations

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
**/*adapter/in/web/dto/response/**Response.java

📄 CodeRabbit inference engine (CLAUDE.md)

Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
**/*application/port/in/**/*{Command,Query}.java

📄 CodeRabbit inference engine (CLAUDE.md)

Command and Query records should be immutable with @NotNull validation and include conversion method toCommand() or toEntity()

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
🧠 Learnings (23)
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Controller.java : Controller classes must only depend on UseCase interfaces, never directly on Repository or Service implementations

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/domain/**/*Entity.java : Entity classes must use NoArgsConstructor(access = AccessLevel.PROTECTED), not Setter, and reference only IDs of other domains instead of direct entity references

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/domain/**/*.java : State changes in Entity classes must be performed through domain methods, not via setters

Applied to files:

  • src/main/java/com/umc/product/organization/domain/School.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Cross-domain communication from services must use Query UseCase interfaces (GetUserInfoUseCase, GetOrganizationUseCase) for accessing other domains, not direct repository access

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Controller.java : REST API endpoints must follow naming conventions: plural forms for resource names and kebab-case for multi-word paths

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller methods must use RequestMapping with API version path (/api/v1/{domain}), include Operation and Tag for OpenAPI/Swagger documentation

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : All public API endpoints must include OpenAPI/Swagger documentation with Tag and Operation annotations

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Response.java : Response DTO classes should use record type and provide static from() method for conversion from domain/info objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/dto/response/**Response.java : Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateSchoolCommand.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/dto/**/*.java : Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/service/**/*Service.java : Service classes implementing usecases must be in application/service/{command|query}/ packages; command services use {Domain}CommandService, query services use {Domain}QueryService

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/service/**Service.java : Service classes must depend on Port interfaces, not directly on Repository classes

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*{application,adapter}/**Test.java : Unit tests must use ExtendWith(MockitoExtension.class), mock all Port/UseCase dependencies with Mock, and use BDD-style given/when/then structure

Applied to files:

  • src/test/java/com/umc/product/support/DocumentationTest.java
🧬 Code graph analysis (1)
src/main/java/com/umc/product/organization/domain/School.java (6)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/organization/domain/ChapterSchool.java (1)
  • Entity (18-50)
src/main/java/com/umc/product/organization/domain/Gisu.java (1)
  • Entity (16-63)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
src/main/java/com/umc/product/organization/domain/StudyGroupMember.java (1)
  • Entity (19-51)
🔇 Additional comments (7)
src/main/java/com/umc/product/organization/application/port/in/query/dto/ChapterInfo.java (1)

3-4: LGTM!

간결하고 명확한 DTO 구조입니다. record를 사용하여 불변성을 보장하고, 쿼리 결과 전달에 적합합니다.

src/main/java/com/umc/product/organization/domain/School.java (1)

16-37: LGTM!

엔티티 구조가 헥사고날 아키텍처 원칙을 잘 따르고 있습니다:

  • protected 생성자와 Builder 패턴 사용
  • 생성 시점 validation 수행
  • setter 없이 도메인 메서드로 상태 변경
src/docs/asciidoc/api/chapter/chapter.adoc (1)

1-4: LGTM!

REST Docs 문서 구조가 올바르게 작성되었습니다. 테스트 스니펫 참조가 적절하게 구성되어 있습니다.

src/main/java/com/umc/product/organization/application/port/in/query/GetChapterUseCase.java (1)

6-9: LGTM!

UseCase 인터페이스가 헥사고날 아키텍처 원칙을 잘 따르고 있습니다:

  • 올바른 패키지 위치 (application/port/in/query)
  • Get{Domain}UseCase 네이밍 패턴 준수
  • Entity가 아닌 Info DTO 반환
  • 단일 책임 원칙 준수

Based on learnings, UseCase 인터페이스 설계 가이드라인을 모두 충족합니다.

src/main/java/com/umc/product/organization/adapter/in/web/dto/response/ChapterListResponse.java (1)

6-27: Response DTO 구조가 가이드라인을 잘 따르고 있습니다.

불변 레코드 구조와 static from() 팩토리 메서드를 사용하여 도메인 계층의 정보 객체를 깔끔하게 변환하고 있습니다. 중첩된 ChapterItem 레코드도 동일한 패턴을 따라 일관성이 좋습니다.

src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java (1)

7-27: LGTM! Response DTO 구조가 올바릅니다.

record 타입을 사용하고 static from() 메서드로 도메인 객체(SchoolInfo)를 변환하는 패턴이 코딩 가이드라인과 일치합니다.

src/test/java/com/umc/product/support/DocumentationTest.java (1)

21-53: 테스트 설정이 올바르게 구성되었습니다

새로 추가된 컨트롤러들(SchoolController, SchoolQueryController, ChapterQueryController)을 명시적으로 나열하고, 필요한 모든 의존성(JwtTokenProvider, UseCase들)을 MockitoBean으로 주입하여 테스트 환경을 정확하게 설정했습니다.

Comment on lines +12 to +15
@RestController
@RequestMapping("/api/v1/admin/chapters")
@RequiredArgsConstructor
public class ChapterQueryController {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OpenAPI/Swagger 문서화 누락

컨트롤러 클래스에 @Tag 어노테이션이 누락되었습니다. 코딩 가이드라인에 따르면 모든 public API 엔드포인트는 OpenAPI/Swagger 문서화를 포함해야 합니다.

📝 문서화 추가 제안
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 @RestController
 @RequestMapping("/api/v1/admin/chapters")
 @RequiredArgsConstructor
+@Tag(name = "Chapter", description = "지부 관리 API")
 public class ChapterQueryController {

Based on learnings, Controller는 OpenAPI 문서화를 위한 @tag@operation 어노테이션을 반드시 포함해야 합니다.

📝 Committable suggestion

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

Suggested change
@RestController
@RequestMapping("/api/v1/admin/chapters")
@RequiredArgsConstructor
public class ChapterQueryController {
import io.swagger.v3.oas.annotations.tags.Tag;
@RestController
@RequestMapping("/api/v1/admin/chapters")
@RequiredArgsConstructor
@Tag(name = "Chapter", description = "지부 관리 API")
public class ChapterQueryController {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
around lines 12 - 15, The ChapterQueryController class is missing OpenAPI
annotations; annotate the controller class ChapterQueryController with @Tag
(e.g., @Tag(name = "Chapters", description = "Admin chapter APIs")) and add
@Operation annotations to each public endpoint method in this controller with
concise summaries; also add the necessary imports
(io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation) so the endpoints are included in
Swagger/OpenAPI documentation.

Comment on lines +19 to +23
@GetMapping
public ChapterListResponse getAllChapter() {
List<ChapterInfo> chapters = getChapterUseCase.getAllChapter();
return ChapterListResponse.from(chapters);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OpenAPI 문서화 및 표준 응답 형식 누락

두 가지 주요 이슈가 있습니다:

  1. @operation 어노테이션 누락: 엔드포인트 문서화를 위한 @operation이 필요합니다.

  2. ApiResponse 형식 미사용: 코딩 가이드라인에 따르면 "All API responses must follow the standard ApiResponse format with success boolean, data, and error fields"입니다. 현재 ChapterListResponse를 직접 반환하고 있습니다.

📝 수정 제안
+import io.swagger.v3.oas.annotations.Operation;
+import com.umc.product.global.response.ApiResponse;
+
     @GetMapping
-    public ChapterListResponse getAllChapter() {
+    @Operation(summary = "지부 목록 조회", description = "전체 지부 목록을 조회합니다.")
+    public ApiResponse<ChapterListResponse> getAllChapter() {
         List<ChapterInfo> chapters = getChapterUseCase.getAllChapter();
-        return ChapterListResponse.from(chapters);
+        return ApiResponse.success(ChapterListResponse.from(chapters));
     }

Based on learnings, Controller 메서드는 @operation 어노테이션과 함께 표준 ApiResponse 형식을 사용해야 합니다.

📝 Committable suggestion

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

Suggested change
@GetMapping
public ChapterListResponse getAllChapter() {
List<ChapterInfo> chapters = getChapterUseCase.getAllChapter();
return ChapterListResponse.from(chapters);
}
import io.swagger.v3.oas.annotations.Operation;
import com.umc.product.global.response.ApiResponse;
@GetMapping
@Operation(summary = "지부 목록 조회", description = "전체 지부 목록을 조회합니다.")
public ApiResponse<ChapterListResponse> getAllChapter() {
List<ChapterInfo> chapters = getChapterUseCase.getAllChapter();
return ApiResponse.success(ChapterListResponse.from(chapters));
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/ChapterQueryController.java
around lines 19 - 23, The getAllChapter endpoint in ChapterQueryController is
missing OpenAPI documentation and is returning ChapterListResponse directly
instead of the standard ApiResponse<T>; add an @Operation annotation to the
getAllChapter method (e.g., annotate ChapterQueryController.getAllChapter) with
a brief summary/description and change the controller to wrap the result into
ApiResponse<ChapterListResponse> (or ApiResponse<List<ChapterInfo>> as per
existing types) before returning so responses contain success, data, and error
fields; ensure the method signature and return type use ApiResponse and update
any static factory or mapper (ChapterListResponse.from(...) usage) to produce
the inner data for the ApiResponse.

Comment on lines +5 to +9
public record UpdateSchoolRequest(
String schoolName,
String chapterId,
String remark
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Request DTO에 입력값 검증 어노테이션이 누락되었습니다.

Web 어댑터 계층의 Request DTO는 반드시 Bean Validation 어노테이션을 포함해야 합니다. 컨트롤러에서 @Valid를 사용할 때 이 어노테이션들이 없으면 입력값 검증이 수행되지 않습니다.

🔒 검증 어노테이션 추가 제안
 package com.umc.product.organization.adapter.in.web.dto.request;

 import com.umc.product.organization.application.port.in.command.dto.UpdateSchoolCommand;
+import jakarta.validation.constraints.NotBlank;

 public record UpdateSchoolRequest(
+        @NotBlank(message = "학교 이름은 필수입니다")
         String schoolName,
+        @NotBlank(message = "지부 ID는 필수입니다")
         String chapterId,
         String remark
 ) {
     public UpdateSchoolCommand toCommand() {
         return new UpdateSchoolCommand(schoolName, chapterId, remark);
     }
 }

As per coding guidelines, Request DTOs must include Valid, NotNull, and NotBlank validation annotations.

📝 Committable suggestion

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

Suggested change
public record UpdateSchoolRequest(
String schoolName,
String chapterId,
String remark
) {
package com.umc.product.organization.adapter.in.web.dto.request;
import com.umc.product.organization.application.port.in.command.dto.UpdateSchoolCommand;
import jakarta.validation.constraints.NotBlank;
public record UpdateSchoolRequest(
@NotBlank(message = "학교 이름은 필수입니다")
String schoolName,
@NotBlank(message = "지부 ID는 필수입니다")
String chapterId,
String remark
) {
public UpdateSchoolCommand toCommand() {
return new UpdateSchoolCommand(schoolName, chapterId, remark);
}
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateSchoolRequest.java
around lines 5 - 9, Update the UpdateSchoolRequest record so its components have
Bean Validation annotations: annotate schoolName and chapterId with appropriate
validation constraints (e.g., @NotBlank for non-empty strings, or
@NotNull/@NotBlank as your domain requires) and add validation to remark if it
must not be empty; ensure you import the javax.validation (or
jakarta.validation) annotations used so @Valid on the controller will trigger
validation for UpdateSchoolRequest.

Comment on lines 23 to 38
@PostMapping()
public void createSchool(@RequestBody @Valid CreateSchoolRequest createSchoolRequest) {

manageSchoolUseCase.register(createSchoolRequest.toCommand());

}

@PatchMapping("/{schoolId}")
public void updateSchool(@PathVariable Long schoolId, @RequestBody @Valid UpdateSchoolRequest updateSchoolRequest) {
manageSchoolUseCase.updateSchool(updateSchoolRequest.toCommand());
}

@DeleteMapping("/{schoolId}")
public void deleteSchool(@PathVariable Long schoolId) {
manageSchoolUseCase.deleteSchool(schoolId);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

API 응답 형식 및 문서화 개선 필요

두 가지 주요 이슈가 있습니다:

  1. ApiResponse 형식 미사용: 코딩 가이드라인에 따르면 모든 API 응답은 표준 ApiResponse<T> 형식(success, data, error 필드 포함)을 따라야 합니다. 현재 모든 메서드가 void를 반환하고 있습니다.

  2. @operation 애노테이션 누락: 각 엔드포인트에 @Operation 애노테이션이 필요합니다.

As per coding guidelines: All API responses must follow the standard ApiResponse format with success boolean, data, and error fields.

📝 제안하는 수정사항
+import io.swagger.v3.oas.annotations.Operation;
+import com.umc.product.common.dto.response.ApiResponse;
+
+    @Operation(summary = "학교 등록", description = "새로운 학교를 등록합니다")
     @PostMapping()
-    public void createSchool(@RequestBody @Valid CreateSchoolRequest createSchoolRequest) {
+    public ApiResponse<Void> createSchool(@RequestBody @Valid CreateSchoolRequest createSchoolRequest) {
         manageSchoolUseCase.register(createSchoolRequest.toCommand());
+        return ApiResponse.success();
     }

+    @Operation(summary = "학교 정보 수정", description = "학교 정보를 수정합니다")
     @PatchMapping("/{schoolId}")
-    public void updateSchool(@PathVariable Long schoolId, @RequestBody @Valid UpdateSchoolRequest updateSchoolRequest) {
+    public ApiResponse<Void> updateSchool(@PathVariable Long schoolId, @RequestBody @Valid UpdateSchoolRequest updateSchoolRequest) {
         manageSchoolUseCase.updateSchool(updateSchoolRequest.toCommand());
+        return ApiResponse.success();
     }

+    @Operation(summary = "학교 삭제", description = "학교를 삭제합니다")
     @DeleteMapping("/{schoolId}")
-    public void deleteSchool(@PathVariable Long schoolId) {
+    public ApiResponse<Void> deleteSchool(@PathVariable Long schoolId) {
         manageSchoolUseCase.deleteSchool(schoolId);
+        return ApiResponse.success();
     }
📝 Committable suggestion

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

Suggested change
@PostMapping()
public void createSchool(@RequestBody @Valid CreateSchoolRequest createSchoolRequest) {
manageSchoolUseCase.register(createSchoolRequest.toCommand());
}
@PatchMapping("/{schoolId}")
public void updateSchool(@PathVariable Long schoolId, @RequestBody @Valid UpdateSchoolRequest updateSchoolRequest) {
manageSchoolUseCase.updateSchool(updateSchoolRequest.toCommand());
}
@DeleteMapping("/{schoolId}")
public void deleteSchool(@PathVariable Long schoolId) {
manageSchoolUseCase.deleteSchool(schoolId);
}
import io.swagger.v3.oas.annotations.Operation;
import com.umc.product.common.dto.response.ApiResponse;
@Operation(summary = "학교 등록", description = "새로운 학교를 등록합니다")
@PostMapping()
public ApiResponse<Void> createSchool(@RequestBody @Valid CreateSchoolRequest createSchoolRequest) {
manageSchoolUseCase.register(createSchoolRequest.toCommand());
return ApiResponse.success();
}
@Operation(summary = "학교 정보 수정", description = "학교 정보를 수정합니다")
@PatchMapping("/{schoolId}")
public ApiResponse<Void> updateSchool(@PathVariable Long schoolId, @RequestBody @Valid UpdateSchoolRequest updateSchoolRequest) {
manageSchoolUseCase.updateSchool(updateSchoolRequest.toCommand());
return ApiResponse.success();
}
@Operation(summary = "학교 삭제", description = "학교를 삭제합니다")
@DeleteMapping("/{schoolId}")
public ApiResponse<Void> deleteSchool(@PathVariable Long schoolId) {
manageSchoolUseCase.deleteSchool(schoolId);
return ApiResponse.success();
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
around lines 23 - 38, The controller methods createSchool, updateSchool, and
deleteSchool currently return void and lack @Operation annotations; change each
method signature to return ApiResponse<T> (use ApiResponse<Void> if no body or
ApiResponse<IdResponse>/appropriate DTO if you want to return created/updated
id), wrap the manageSchoolUseCase call result (or just return
ApiResponse.success(null) on success) into the ApiResponse structure, and add an
@Operation(...) annotation above each method describing the endpoint; update
createSchool, updateSchool, and deleteSchool to return ApiResponse and construct
success/error responses accordingly.

Comment on lines +60 to +64
public void updateEmailDomain(String emailDomain) {
if (StringUtils.isEmpty(emailDomain)) {
this.emailDomain = emailDomain;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

조건문 로직이 반대로 구현됨

StringUtils.isEmpty(emailDomain)이 true일 때(즉, 파라미터가 비어있을 때) emailDomain을 업데이트하고 있습니다. 이는 빈 값으로만 업데이트된다는 의미로, 의도와 반대입니다.

🐛 로직 수정 제안
     public void updateEmailDomain(String emailDomain) {
-        if (StringUtils.isEmpty(emailDomain)) {
+        if (!StringUtils.isEmpty(emailDomain)) {
             this.emailDomain = emailDomain;
         }
     }

혹은 생성 시와 동일한 validation 적용:

     public void updateEmailDomain(String emailDomain) {
-        if (StringUtils.isEmpty(emailDomain)) {
+        if (emailDomain != null && !emailDomain.isBlank()) {
             this.emailDomain = emailDomain;
         }
     }
🤖 Prompt for AI Agents
In @src/main/java/com/umc/product/organization/domain/School.java around lines
60 - 64, In School.updateEmailDomain the conditional is inverted: it updates
this.emailDomain only when StringUtils.isEmpty(emailDomain) is true (empty);
change the check to ensure non-empty input is used (e.g., use
!StringUtils.isEmpty(emailDomain) or the same validation used in the
constructor) so emailDomain is only updated when a valid, non-empty value is
provided.

Comment on lines +40 to +41
fieldWithPath("result.chapters[].id").type(JsonFieldType.STRING).description("지부 ID"),
fieldWithPath("result.chapters[].name").type(JsonFieldType.STRING).description("지부 이름")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

REST Docs 필드 타입이 실제 타입과 불일치합니다.

ChapterInfoid 필드는 Long 타입이지만, REST Docs에서는 STRING으로 문서화되어 있습니다. 테스트 데이터에서도 1L, 2L, 3L로 Long 타입을 사용하고 있으므로 문서화를 NUMBER로 수정해야 합니다.

📄 REST Docs 타입 수정 제안
-                                fieldWithPath("result.chapters[].id").type(JsonFieldType.STRING).description("지부 ID"),
+                                fieldWithPath("result.chapters[].id").type(JsonFieldType.NUMBER).description("지부 ID"),
                                 fieldWithPath("result.chapters[].name").type(JsonFieldType.STRING).description("지부 이름")
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/ChapterQueryControllerTest.java
around lines 40 - 41, The REST Docs field type for ChapterInfo.id is incorrect:
in ChapterQueryControllerTest the fieldWithPath("result.chapters[].id") is
documented as JsonFieldType.STRING but ChapterInfo.id is a Long and test data
uses 1L/2L/3L; update the documentation to use JsonFieldType.NUMBER (or
NUMBER_TYPE constant) for that field so the generated docs match the actual Long
numeric type used by ChapterInfo and the test responses.

Comment on lines +34 to +36
requestFields(fieldWithPath("schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("chapterId").type(JsonFieldType.STRING).description("소속 지부 ID"),
fieldWithPath("remark").type(JsonFieldType.STRING).description("비고"))));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# chapterId의 타입을 확인합니다

# Chapter 엔티티의 ID 타입 확인
echo "=== Chapter 엔티티 ID 타입 ==="
rg -nP --type=java -A3 -B3 'class\s+Chapter\s' | head -50

# CreateSchoolCommand의 chapterId 타입 확인
echo -e "\n=== CreateSchoolCommand의 chapterId 타입 ==="
rg -nP --type=java 'CreateSchoolCommand' | head -20

# CreateSchoolRequest의 chapterId 타입 확인
echo -e "\n=== CreateSchoolRequest의 chapterId 타입 ==="
rg -nP --type=java 'CreateSchoolRequest' | head -20

# schoolId 타입 확인 (비교용)
echo -e "\n=== schoolId 타입 확인 ==="
rg -nP --type=java 'schoolId.*Long|schoolId.*String' | head -20

Repository: UMC-PRODUCT/umc-product-server

Length of output: 3370


🏁 Script executed:

#!/bin/bash

# Read CreateSchoolCommand definition
echo "=== CreateSchoolCommand.java ==="
cat -n src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateSchoolCommand.java

# Read CreateSchoolRequest definition
echo -e "\n=== CreateSchoolRequest.java ==="
cat -n src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateSchoolRequest.java

# Read Chapter entity to see ID type
echo -e "\n=== Chapter.java (ID field) ==="
head -30 src/main/java/com/umc/product/organization/domain/Chapter.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 1992


🏁 Script executed:

#!/bin/bash

# Read School entity to see ID type
echo "=== School.java (ID field) ==="
rg -nP --type=java -A10 'public class School' src/main/java/com/umc/product/organization/domain/School.java | head -30

# Check how schoolId is used in the controller test
echo -e "\n=== schoolId usage in SchoolControllerTest ==="
rg -nP --type=java 'schoolId' src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java | head -20

# Check CreateSchoolResponse to see if it returns Long or String
echo -e "\n=== CreateSchoolResponse ==="
rg -nP --type=java 'CreateSchoolResponse|class.*Response' src/main/java/com/umc/product/organization/adapter/in/web/dto/ -A5 | head -30

Repository: UMC-PRODUCT/umc-product-server

Length of output: 951


chapterId 필드의 타입 일관성 검토 필요

chapterId 필드가 String 타입으로 구현되어 있으나, 동일 시스템 내 다른 엔티티의 ID들(Chapter.id, School.id)은 모두 Long 타입입니다. CreateSchoolRequest와 CreateSchoolCommand 모두에서 chapterIdString으로 정의하고 있으므로 현재 구현은 일관성 있지만, 엔티티 ID 패턴과의 불일치를 검토하세요:

  • Chapter 엔티티: private Long id; (IDENTITY 전략)
  • School 엔티티: private Long id; (IDENTITY 전략)
  • chapterId: String 타입

Chapter ID를 외부에 문자열로 노출하려는 의도라면 코드에 주석으로 명시하거나, 일관성을 위해 Long 타입으로 변경하고 API 문서도 NUMBER로 수정해야 합니다.

🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
around lines 34 - 36, The test documents chapterId as a STRING but Chapter.id
and School.id are Longs; either document intent or make types consistent: update
CreateSchoolRequest and CreateSchoolCommand to use Long chapterId (or add a
comment if string is intentional), change the test's field descriptor
fieldWithPath("chapterId").type(JsonFieldType.NUMBER).description(...) to
NUMBER, and update any related serialization/parsing and API docs to reflect
Long IDs so entity ID types are consistent across Chapter, School, and the
controller payloads.

Comment on lines +45 to +46
CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("동국대학교").chapterId("3")
.remark("신승호 라면이 맛있습니다.").build();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

학교 수정 테스트에서 잘못된 Request DTO를 사용하고 있습니다.

학교 정보 수정 테스트에서 CreateSchoolRequest 대신 UpdateSchoolRequest를 사용해야 합니다. AI 요약에 따르면 UpdateSchoolRequest.toCommand() 메서드가 존재하므로, 수정 작업에는 해당 DTO를 사용하는 것이 의미상 올바릅니다.

🔄 올바른 Request DTO 사용 제안
+import com.umc.product.organization.adapter.in.web.dto.request.UpdateSchoolRequest;
+
     @Test
     void 총괄_학교정보를_수정한다() throws Exception {
         // given // when
         Long schoolId = 1L;
 
-        CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("동국대학교").chapterId("3")
+        UpdateSchoolRequest request = UpdateSchoolRequest.builder().schoolName("동국대학교").chapterId("3")
                 .remark("신승호 라면이 맛있습니다.").build();
📝 Committable suggestion

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

Suggested change
CreateSchoolRequest request = CreateSchoolRequest.builder().schoolName("동국대학교").chapterId("3")
.remark("신승호 라면이 맛있습니다.").build();
import com.umc.product.organization.adapter.in.web.dto.request.UpdateSchoolRequest;
@Test
void 총괄_학교정보를_수정한다() throws Exception {
// given // when
Long schoolId = 1L;
UpdateSchoolRequest request = UpdateSchoolRequest.builder().schoolName("동국대학교").chapterId("3")
.remark("신승호 라면이 맛있습니다.").build();
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
around lines 45 - 46, The test is using CreateSchoolRequest for the
school-update scenario; replace that with UpdateSchoolRequest in
SchoolControllerTest (the update test), construct an UpdateSchoolRequest with
the appropriate fields (e.g., schoolName, chapterId, remark) and call
UpdateSchoolRequest.toCommand() where the test currently uses
CreateSchoolRequest.toCommand() or passes the create DTO to the controller; also
adjust any variable names and assertions to reflect an update flow so the test
exercises the update path using UpdateSchoolRequest.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 43

🤖 Fix all issues with AI agents
In @src/docs/asciidoc/index.adoc:
- Around line 22-24: The Chapter API section ([[Chapter-API]] / "== 지부 API") is
referencing the wrong include path or missing content; update the include to
point to the correct document (replace any include of gisu.adoc with
chapter.adoc or the proper chapter file) so that the Chapter API content is
rendered; locate the include directive near the [[Chapter-API]] block and change
the filename to chapter.adoc (or the correct relative path) and ensure the
included file actually contains the Chapter API content.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java:
- Around line 7-10: Add a logical date-range validation to CreateGisuRequest so
startAt is not after endAt: in CreateGisuRequest (fields number, startAt, endAt)
perform the check before converting to command (e.g., inside toCommand()) and
throw a validation exception (or return an appropriate error) when
startAt.isAfter(endAt); alternatively, implement a custom validation annotation
applied to the startAt/endAt fields and enforce the same rule during DTO
validation.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java:
- Around line 6-8: DeleteSchoolsRequest에 toCommand() 변환 메서드가 빠져 있어 컨트롤러가
request.schoolIds()를 직접 사용해 계층 결합도가 높습니다; 추가로 DeleteSchoolsCommand 레코드(예:
application.port.in.command.dto.DeleteSchoolsCommand)를 생성한 뒤
DeleteSchoolsRequest에 public DeleteSchoolsCommand toCommand() 메서드를 구현하여 내부
List<Long> schoolIds 값을 새 DeleteSchoolsCommand 인스턴스로 변환하도록 하고, 컨트롤러에서는
request.schoolIds() 대신 request.toCommand()를 전달하도록 수정하세요.
- Line 7: The DeleteSchoolsRequest currently uses @NotEmpty on List<Long>
schoolIds which only checks the list is non-empty but not that individual Long
elements are non-null; update the field to List<@NotNull Long> schoolIds
(keeping @NotEmpty on the list) so element-level nulls are validated, add the
appropriate import for javax.validation.constraints.NotNull (or jakarta
equivalent) and ensure your validator is configured to validate container
element constraints.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java:
- Around line 5-8: The SchoolListRequest record is missing validation
annotations for its fields; add appropriate javax.validation constraints to the
record components (e.g., annotate keyword with @NotBlank or @Nullable/@Size if
optional, and chapterId with @NotNull or @Positive if it must be
present/positive) and ensure the controller maps optional query params with
@RequestParam(required = false) when applicable; update imports for
javax.validation (or jakarta.validation) annotations and adjust controller
parameter handling to match the DTO's optional/required semantics.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java:
- Around line 7-10: The request DTO UpdateGisuRequest currently lacks validation
that startAt is before endAt; add validation at the DTO level by implementing a
compact constructor (record canonical constructor) or a custom validation
annotation on UpdateGisuRequest to check that startAt and endAt are non-null and
startAt.isBefore(endAt) (or startAt.isEqual(endAt) if equality allowed) and
throw a descriptive exception (e.g., IllegalArgumentException or a
ConstraintViolation) when the condition fails so clients get immediate feedback
before toCommand is used.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java:
- Around line 9-12: Rename the static factory method
SchoolDetailResponse.of(SchoolInfo info) to SchoolDetailResponse.from(SchoolInfo
info) to comply with the project naming convention; update the method signature
and its internal return to remain the same, then search for and replace all call
sites that use SchoolDetailResponse.of(...) to use
SchoolDetailResponse.from(...) so compilation and behavior remain unchanged.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java:
- Around line 9-12: Rename the static factory method
SchoolListItemResponse.of(SchoolListItemInfo) to
SchoolListItemResponse.from(SchoolListItemInfo) and update all call sites (e.g.,
in SchoolQueryController and any other usages) to use from(...) instead of
of(...); ensure the method signature and return behavior remain identical and
that imports/compilation are updated accordingly.

In
@src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java:
- Around line 33-36: The deleteGisu endpoint currently lacks OpenAPI metadata
and does not return the standardized ApiResponse<T> wrapper; update the
GisuController.deleteGisu method to return ApiResponse<Void> (or
ResponseEntity<ApiResponse<Void>>) instead of void, wrap the result or success
status in your ApiResponse type, and add an @Operation annotation with a concise
summary/description and appropriate @ApiResponse annotations for success (e.g.,
200/204) and possible errors; ensure the method still calls
manageGisuUseCase.deleteGisu(gisuId) internally and maps exceptions/status codes
into the ApiResponse structure.
- Around line 23-26: The createGisu controller method currently returns a raw
Long; change its signature to return ApiResponse<Long> and wrap the result of
manageGisuUseCase.register(request.toCommand()) in ApiResponse.success(...) (or
your project's equivalent factory), add the @Operation(...) annotation above the
method with an appropriate summary/description, and update imports for
ApiResponse and Operation; keep the endpoint logic using CreateGisuRequest and
manageGisuUseCase.register(request.toCommand()) but ensure the response follows
ApiResponse<T> format.
- Around line 16-19: Add OpenAPI annotations to GisuController by annotating the
class GisuController with @Tag (providing a name and optional description) and
by adding @Operation to each public request handler method in that controller
(every public endpoint method in GisuController). Ensure you import
io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation and place meaningful summary/description
values in the @Operation annotations for each public API method.

In
@src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java:
- Around line 10-13: The controller class GisuQueryController is missing OpenAPI
annotations; add a class-level @Tag (e.g., @Tag(name = "Gisu", description =
"Gisu admin queries")) above GisuQueryController and annotate each public
endpoint method in this class with @Operation(summary = "...", description =
"...") providing concise summaries/descriptions; ensure you import the
annotations from io.swagger.v3.oas.annotations and apply @Operation to every
public handler method (e.g., any methods like get..., list..., find...) within
GisuQueryController so the OpenAPI docs include the controller and its
endpoints.
- Around line 17-20: The controller method getGisuList in GisuQueryController
currently returns GisuListResponse directly; wrap the result in your standard
ApiResponse<T> by returning
ApiResponse.success(GisuListResponse.from(getGisuUseCase.getList())) (or
equivalent factory) and add an @Operation annotation on the getGisuList method
to document the endpoint; ensure the method signature and import use
ApiResponse<GisuListResponse> and adjust any callers/tests accordingly.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java:
- Around line 34-37: The deleteSchool endpoint lacks an OpenAPI @Operation
annotation and should return a standardized ApiResponse wrapper instead of void;
update the deleteSchool method to add an @Operation(...) annotation (provide
summary/description and responses) and change its signature to return
ApiResponse<Void> (or ResponseEntity<ApiResponse<Void>> if you use HTTP status
control), calling manageSchoolUseCase.deleteSchool(schoolId) then return a
success ApiResponse instance (or ResponseEntity with appropriate HTTP status) to
conform with your API response model.
- Around line 24-27: The createSchool controller currently returns void and
doesn't follow the ApiResponse<T> wrapper or have an @Operation annotation;
change the method signature of createSchool to return ApiResponse<Void> (or
ApiResponse<CreateSchoolResponse> if you want to return created info), wrap the
result of manageSchoolUseCase.register(...) in the standard ApiResponse success
envelope, and add an @Operation(...) annotation with a brief summary/description
above the method; keep the @PostMapping and @RequestBody/@Valid usage and ensure
any exceptions still propagate into the global error mapper that produces
ApiResponse errors.
- Around line 39-42: Add OpenAPI metadata and wrap the method response in the
project's ApiResponse<T> wrapper: annotate the SchoolController.deleteSchools
method with an @Operation (provide a concise summary and description) and
appropriate @ApiResponses/@ApiResponse entries, and change the signature from
void deleteSchools(...) to return ApiResponse<Void> (or ApiResponse<EmptyDto>
used elsewhere) and return a success ApiResponse after calling
manageSchoolUseCase.deleteSchools(request.schoolIds()); ensure
DeleteSchoolsRequest and manageSchoolUseCase.deleteSchools(...) behavior remains
unchanged.
- Around line 29-32: The controller method updateSchool is missing the OpenAPI
@Operation/@ApiResponse annotations and also ignores the path variable schoolId
when building the command; update it to accept/return the standardized
ApiResponse<T> (e.g., ApiResponse<Void> or ApiResponse<UpdateSchoolResponse>)
and add @Operation(summary="...") plus appropriate @ApiResponses/@ApiResponse
annotations; fix the command creation so the ID is included (change use of
UpdateSchoolRequest.toCommand() to a variant that accepts schoolId or set the id
on the command before calling manageSchoolUseCase.updateSchool), and adapt
method signature and return to wrap the result in ApiResponse.
- Around line 29-32: The updateSchool method currently ignores the PathVariable
schoolId; modify updateSchool to pass schoolId into the command creation (e.g.,
call updateSchoolRequest.toCommand(schoolId) or construct a command with
schoolId) and then call manageSchoolUseCase.updateSchool(command); also verify
and, if necessary, change UpdateSchoolRequest.toCommand(...) to accept a Long
schoolId parameter and populate the command's id field so the correct school is
updated.
- Around line 17-20: The SchoolController class is missing OpenAPI annotations;
add a class-level @Tag annotation on the SchoolController to describe the
controller (e.g., name and description) and annotate every public API handler
method in SchoolController with an @Operation annotation providing a summary and
description; ensure you import io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation and place @Tag above the class
declaration and @Operation immediately above each public endpoint method (all
request-mapping/handler methods) to satisfy the documentation requirement.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java:
- Around line 24-28: The getSchoolList method in SchoolQueryController currently
returns PageResponse<SchoolListItemResponse> instead of the standard
ApiResponse<T> and lacks an @Operation annotation; change the method signature
of getSchoolList to return ApiResponse<PageResponse<SchoolListItemResponse>>,
wrap the existing PageResponse.of(...) result inside ApiResponse with
success=true, data=<pageResponse>, error=null (or ApiError empty), and add an
appropriate @Operation(...) annotation above the method; ensure
imports/reference to ApiResponse, PageResponse, SchoolListItemResponse,
getSchoolUseCase.getList(...) and @Operation are updated accordingly.
- Around line 30-36: The controller method getSchoolDetail in
SchoolQueryController returns SchoolDetailResponse directly and lacks the
Swagger @Operation annotation; change the method to return
ApiResponse<SchoolDetailResponse> (wrap SchoolDetailResponse.of(schoolInfo) with
ApiResponse.success or the project’s standard ApiResponse factory), add the
required @Operation(...) annotation above the method, and update
imports/signature accordingly so the endpoint follows the standard
ApiResponse<T> format while keeping the method name and use of
getSchoolUseCase.getSchoolDetail(schoolId) unchanged.
- Around line 17-20: Add OpenAPI/Swagger annotations to document the controller
and its endpoints: annotate the SchoolQueryController class with @Tag (e.g.,
name="School API", description="School queries for admin") and add @Operation
annotations to each public endpoint method (e.g., methods like getAllSchools(),
getSchoolById(), searchSchools()) with summary and description values; ensure
the controller imports io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation so the APIs are properly documented.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java:
- Around line 5-6: Add null-validation and a conversion method to the
CreateGisuCommand record: annotate each component (number, startAt, endAt) with
@NotNull (import javax.validation.constraints.NotNull) and implement a
toEntity() method (e.g., public Gisu toEntity()) that constructs and returns the
domain Gisu entity using the record components (number, startAt, endAt);
reference the CreateGisuCommand record and the toEntity() method name so callers
convert the command to the domain object before passing into the service layer.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java:
- Around line 5-6: UpdateGisuCommand record lacks validation on its fields; add
@NotNull to each component (gisuId, startAt, endAt) on the record declaration so
framework validation triggers before service layer; ensure you import the
appropriate validation annotation (e.g., javax.validation.constraints.NotNull or
jakarta.validation.constraints.NotNull) and apply it to the record components in
the UpdateGisuCommand declaration.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java:
- Around line 7-16: ManageSchoolUseCase currently bundles four responsibilities
(register, update, delete single, delete bulk); split it into four
single-responsibility interfaces: RegisterSchoolUseCase (void
register(CreateSchoolCommand)), UpdateSchoolUseCase (void
updateSchool(UpdateSchoolCommand)), DeleteSchoolUseCase (void
deleteSchool(Long)), and DeleteSchoolsUseCase (void deleteSchools(List<Long>));
update any implementation class (e.g., SchoolCommandService) to implement the
specific new interfaces it provides and update DI/injection points and tests to
depend on the minimal interface(s) they need instead of ManageSchoolUseCase, and
remove ManageSchoolUseCase or keep it deprecated if you need a transitional
adapter.

In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java:
- Around line 3-6: Document nullability for the search condition fields in the
SchoolSearchCondition record: explicitly state in a JavaDoc on the
SchoolSearchCondition record that String keyword and Long chapterId are optional
and may be null (or, alternatively, annotate them with @Nullable if your project
uses nullability annotations) and describe expected semantics when null (e.g.,
absence of keyword means match all, null chapterId means no chapter filter) so
callers know how to handle nulls.

In
@src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java:
- Around line 6-11: GetGisuUseCase currently mixes two responsibilities (list
and single retrieval); split it into two single-purpose interfaces: create
GetGisuListUseCase with List<GisuInfo> getList() and GetGisuByIdUseCase with
GisuInfo getById(Long gisuId), update any implementing classes to implement the
appropriate new interface(s) (replace GetGisuUseCase implementations), and
change injection sites (e.g., GisuQueryController) to depend on
GetGisuListUseCase and GetGisuByIdUseCase respectively so each use case
interface has a single responsibility.
- Line 10: Implement the missing getById flow by creating a service class
GisuQueryService that implements GetGisuUseCase and injects LoadGisuPort; in
GisuQueryService implement getById(Long gisuId) to call
loadGisuPort.findById(gisuId).orElseThrow(() -> new
BusinessException(Domain.COMMON, OrganizationErrorCode.GISU_NOT_FOUND)) and map
the entity to GisuInfo; add the new OrganizationErrorCode.GISU_NOT_FOUND enum
value for the not-found case; and expose the use case via a controller endpoint
(e.g., GET /gisu/{id}) that calls GetGisuUseCase.getById and returns the
GisuInfo.

In
@src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java:
- Around line 10-15: The interface GetSchoolUseCase mixes two responsibilities
(list and detail retrieval); split it into two focused interfaces: create
GetSchoolListUseCase exposing Page<SchoolListItemInfo>
getList(SchoolSearchCondition, Pageable) and GetSchoolDetailUseCase exposing
SchoolInfo getSchoolDetail(Long), update any classes that implement or depend on
GetSchoolUseCase to implement or inject the new interfaces (replace references
to GetSchoolUseCase with the appropriate GetSchoolListUseCase or
GetSchoolDetailUseCase) and move/import the related DTO types
(SchoolListItemInfo, SchoolSearchCondition, SchoolInfo) into the corresponding
interface files/packages.

In
@src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java:
- Around line 1-9: The service class is in the wrong package and uses the wrong
name for hexagonal conventions: move the class from the current package to
application.service.command and rename the class from SchoolService to
SchoolCommandService; update the package declaration to
application.service.command and any imports/usages of ManageSchoolUseCase,
CreateSchoolCommand, and UpdateSchoolCommand to reference the new class name and
location.
- Around line 11-25: The service methods in SchoolService are empty and missing
Port Out dependencies; inject the LoadSchoolPort and SaveSchoolPort (and any
DeleteSchoolPort if present) via constructor into SchoolService, mark mutating
methods (register, updateSchool, deleteSchool, deleteSchools) with
@Transactional, implement register(CreateSchoolCommand) by mapping the command
to a School domain entity and calling savePort.save/create, implement
updateSchool(UpdateSchoolCommand) by loading the existing School via
loadPort.loadById, applying updates on the domain object and calling
savePort.save, implement deleteSchool(Long) by delegating to savePort.deleteById
or deletePort.delete and implement deleteSchools(List<Long>) to iterate or call
a bulk-delete on the port; ensure null/not-found checks throw appropriate domain
exceptions and use the port interfaces (LoadSchoolPort, SaveSchoolPort,
DeleteSchoolPort) rather than repositories.

In @src/main/java/com/umc/product/organization/domain/Gisu.java:
- Line 25: Add validation for the Gisu.number field inside the existing validate
method of the Gisu class: ensure number is not null and is positive (greater
than 0), and throw the appropriate OrganizationErrorCode exceptions (use
GISU_NUMBER_REQUIRED when null and GISU_NUMBER_INVALID when non-positive); also
add these two constants GISU_NUMBER_REQUIRED and GISU_NUMBER_INVALID to
OrganizationErrorCode so the validate method can reference them.

In
@src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java:
- Around line 26-42: The test 신규_기수를_추가한다 currently stubs
manageGisuUseCase.register to return 1L but never asserts the response body;
update the test to verify the controller returns the created resource ID (1) in
the response. After performing mockMvc.perform (ResultActions result), add
assertions on the response content (e.g., jsonPath or content) to check the ID
field equals 1 and the status remains OK, referencing the existing ResultActions
result, mockMvc.perform call, and the manageGisuUseCase.register stub so the
test validates the returned ID.
- Around line 38-41: Test documents the "number" field as JsonFieldType.STRING
but the actual request object CreateGisuRequest uses a Long for the first param
(see the 9L in its constructor); update the REST Docs assertion in
GisuControllerTest (the call to
result.andExpect(...).andDo(restDocsHandler.document(...)) and specifically the
requestFields/fieldWithPath("number")) to use JsonFieldType.NUMBER so the
documented type matches the Long used by CreateGisuRequest.

In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java:
- Around line 24-40: Refactor the test method 총괄_신규학교를_추가한다() to clearly
separate Given, When, Then: put setup/fixture creation (CreateSchoolRequest
request = ...) under a "Given" block, perform the action (ResultActions result =
mockMvc.perform(...) ) under a "When" block, and keep assertions and restDocs
verification (result.andExpect(...).andDo(...)) under a "Then" block; update
comments to label each section and, if helpful, rename intermediate variables
for clarity (e.g., request for Given, result for When) while leaving method
names and assertions unchanged.
- Around line 42-61: The test method 총괄_학교정보를_수정한다 in SchoolControllerTest mixes
Given and When; refactor it to clearly separate the stages by moving setup into
a distinct "given" section (create Long schoolId and CreateSchoolRequest),
perform the action in a single "when" section that calls mockMvc.perform and
assigns the ResultActions, and then assert in the "then" section using
result.andExpect(...). Add clear comments or blank lines labeled // given, //
when, // then around CreateSchoolRequest, the mockMvc.perform call, and the
result assertions to make the structure explicit.
- Around line 63-76: The test method 총괄_학교를_제거한다 currently mixes Given/When/Then
steps; refactor to clearly separate them by putting setup/mock preparations
under a "given" block (e.g., any stubbing for services), performing the action
in a "when" block by assigning the outcome of
mockMvc.perform(delete("/api/v1/admin/schools/{schoolId}", schoolId)) to a
variable, and placing assertions and documentation in a "then" block (calling
result.andExpect(...).andDo(...)); update the test method 총괄_학교를_제거한다() to
follow this structure and add explicit comments or blank lines to demarcate
given/when/then around the mockMvc.perform and result assertions.

In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java:
- Line 28: The test method 이름 학교_목록을_조회합니다 in SchoolQueryControllerTest is too
generic; rename it to follow the {behavior}_{condition}_{result} pattern (e.g.,
유효한_파라미터로_학교_목록_조회시_성공) and apply the same pattern to the other test at the same
file (the method at line ~87, currently 학교_상세정보를_조회합니다) so names clearly express
behavior, condition, and expected result; update any references to these method
names (annotations, test runner) so the tests still execute.
- Line 88: In SchoolQueryControllerTest update the test setup comment that
currently reads "give" to the correct "given" (the comment near the test setup
in class SchoolQueryControllerTest) so the section heading follows the standard
"given-when-then" pattern; locate the comment in the test method block and
change the typo to "given".
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e09723 and 544e2c0.

📒 Files selected for processing (31)
  • src/docs/asciidoc/api/gisu/gisu.adoc
  • src/docs/asciidoc/api/school/school.adoc
  • src/docs/asciidoc/index.adoc
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
🧰 Additional context used
📓 Path-based instructions (10)
src/**

⚙️ CodeRabbit configuration file

src/**: 다음 항목들을 꼼꼼하게 검토해줘.

  1. 예외 처리
  • 예외가 적절히 처리되었는지 확인해줘. (try-catch, throws, ExceptionAdvice)
  • 공통 예외 처리 모듈(예: GlobalHandler, ApiResponse 등)을 잘 활용했는지 확인.
  • RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토.
  • 예외 메시지에 민감 정보(DB 정보, 사용자 정보 등)가 노출되지 않게 했는지 점검.
  1. 코드 품질 & 가독성
  • 메소드/클래스가 단일 책임 원칙(SRP)에 맞게 구성되어 있는지.
  • 중복 코드가 있는 경우, 유틸/공통 컴포넌트로 추출 가능한지.
  • 의미 있는 변수명과 메소드명을 사용했는지.
  • 매직 넘버, 하드코딩된 값이 존재하는지 점검.
  1. 성능 및 효율성
  • 불필요한 DB 쿼리 호출, N+1 문제 가능성이 있는지 확인.
  • Stream, loop, recursion 사용 시 시간복잡도/메모리 효율성을 고려했는지.
  • 캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지.
  1. 트랜잭션 관리
  • @transactional이 필요한 메소드에 누락되지 않았는지.
  • 읽기 전용 트랜잭션(readOnly = true)을 적절히 사용했는지.
  • DB 일관성, 롤백 정책이 올바른지 검토.
  1. 입력 검증 및 보안
  • @Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지.
  • 비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지.
  1. 테스트
  • 단위 테스트가 충분히 작성되었는지, 핵심 로직의 검증이 누락되지 않았는지.
  • Mocking을 통한 독립 테스트 구조를 유지했는지.
  • 경계값 테스트, 예외 케이스 테스트가 포함되어 있는지.
  1. 구조 및 설계
  • Controller, Service, Repository 등 계층 구조가 올바르게 나뉘어 있는지.
  • DTO, Entity, Domain 객체 간 변환 로직이 명확하고 중복되지 않는지.
  • Config 클래스에서 Bean 등록이 과도하거나 순환 참조 위험이 없는지.

Files:

  • src/docs/asciidoc/index.adoc
  • src/docs/asciidoc/api/gisu/gisu.adoc
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/docs/asciidoc/api/school/school.adoc
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java
**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Files:

  • src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java
  • src/main/java/com/umc/product/organization/domain/Gisu.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java
**/*adapter/in/web/dto/**/*.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request/Response DTOs must be in adapter/in/web/dto/{request|response}/ packages; request DTOs use {Action}{Domain}Request pattern, response DTOs use {Domain}{Purpose}Response pattern

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
**/*adapter/in/web/dto/request/**Request.java

📄 CodeRabbit inference engine (CLAUDE.md)

Request DTOs must include @Valid, @NotNull, and @notblank validation annotations; include toCommand() conversion method to command record

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
**/*adapter/in/web/dto/response/**Response.java

📄 CodeRabbit inference engine (CLAUDE.md)

Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
**/*application/port/in/**/*UseCase.java

📄 CodeRabbit inference engine (CLAUDE.md)

UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
**/*application/port/in/**/*{Command,Query}.java

📄 CodeRabbit inference engine (CLAUDE.md)

Command and Query records should be immutable with @NotNull validation and include conversion method toCommand() or toEntity()

Files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
**/*Test.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*Test.java: Test methods must use descriptive Korean names following {behavior}{condition}{result} pattern for clarity
Test methods must follow Given-When-Then structure with clear setup, execution, and assertion sections

Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Files:

  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java
  • src/test/java/com/umc/product/support/DocumentationTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
  • src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java
**/*adapter/in/web/**Controller.java

📄 CodeRabbit inference engine (CLAUDE.md)

**/*adapter/in/web/**Controller.java: Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces
Controller methods must use @RequestMapping with API version path (/api/v1/{domain}), include @operation and @tag for OpenAPI/Swagger documentation
All API responses must follow the standard ApiResponse format with success boolean, data, and error fields
All public API endpoints must include OpenAPI/Swagger documentation with @tag and @operation annotations

Files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java
**/domain/**/*.java

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

State changes in Entity classes must be performed through domain methods, not via setters

Files:

  • src/main/java/com/umc/product/organization/domain/Gisu.java
🧠 Learnings (28)
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/**Info.java : Info DTOs for cross-domain responses must use {Domain}Info naming pattern and be immutable records

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should use record type and include validation annotations; should not include userId field

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/dto/request/**Request.java : Request DTOs must include Valid, NotNull, and NotBlank validation annotations; include toCommand() conversion method to command record

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/in/**Command.java : UseCase Command parameter objects should be defined as records, not plain classes

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/dto/response/**Response.java : Response DTOs must use records or immutable objects with static from() method for conversion from domain Info objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Response.java : Response DTO classes should use record type and provide static from() method for conversion from domain/info objects

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolDetailResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java
  • src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/in/**/*UseCase.java : UseCase interfaces must be defined in application/port/in/{command|query}/ packages; command usecases use {Action}{Domain}UseCase pattern, query usecases use Get{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase (Port In) interfaces should have single responsibility with one use case per interface, with names following {Action}{Domain}UseCase pattern

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Request.java : Request DTO classes should provide toCommand() method to convert to UseCase command objects

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/in/**UseCase.java : UseCase interfaces should return Info/DTO objects, not Entity objects directly

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Controller.java : Controller classes must only depend on UseCase interfaces, never directly on Repository or Service implementations

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/in/**Command.java : Command DTO naming pattern: {Action}{Domain}Command; must be immutable records with validation

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*Test.java : Test method naming must be descriptive in Korean or English with underscores (e.g., 챌린저_등록_성공, user_registration_fails_for_duplicate_email)

Applied to files:

  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller methods must use RequestMapping with API version path (/api/v1/{domain}), include Operation and Tag for OpenAPI/Swagger documentation

Applied to files:

  • src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : All public API endpoints must include OpenAPI/Swagger documentation with Tag and Operation annotations

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : All API responses must follow the standard ApiResponse<T> format with success boolean, data, and error fields

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java
  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/in/**/*{Command,Query}.java : Command and Query records should be immutable with NotNull validation and include conversion method toCommand() or toEntity()

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/adapter/in/**Controller.java : REST API endpoints must follow naming conventions: plural forms for resource names and kebab-case for multi-word paths

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/in/web/**Controller.java : Controller classes must be in adapter/in/web/ package, use {Domain}Controller naming, and only depend on UseCase interfaces

Applied to files:

  • src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Cross-domain communication from services must use Query UseCase interfaces (GetUserInfoUseCase, GetOrganizationUseCase) for accessing other domains, not direct repository access

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java
  • src/main/java/com/umc/product/organization/application/port/in/query/GetGisuUseCase.java
  • src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/service/**/*Service.java : Service classes implementing usecases must be in application/service/{command|query}/ packages; command services use {Domain}CommandService, query services use {Domain}QueryService

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/service/**Service.java : Service classes must depend on Port interfaces, not directly on Repository classes

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/*.java : Package structure must follow hexagonal architecture with domain/, application/{port/in, port/out, service}/, and adapter/{in, out}/ directories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*application/port/out/**Port.java : Port interfaces (Load{Domain}Port, Save{Domain}Port) must be defined in application/port/out/ package and return domain entities or optionals

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Applies to **/application/port/out/**Port.java : Port (Port Out) interfaces must be separated into Load and Save ports following Interface Segregation Principle

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:22:39.486Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-04T08:22:39.486Z
Learning: Service classes should not have multiple responsibilities (God Service anti-pattern); separate by feature/command type

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*adapter/out/persistence/**PersistenceAdapter.java : Persistence adapters (in adapter/out/persistence/) must implement Load{Domain}Port and Save{Domain}Port interfaces and delegate to JPA repositories

Applied to files:

  • src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java
📚 Learning: 2026-01-04T08:23:08.581Z
Learnt from: CR
Repo: UMC-PRODUCT/umc-product-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T08:23:08.581Z
Learning: Applies to **/*{application,adapter}/**Test.java : Unit tests must use ExtendWith(MockitoExtension.class), mock all Port/UseCase dependencies with Mock, and use BDD-style given/when/then structure

Applied to files:

  • src/test/java/com/umc/product/support/DocumentationTest.java
🧬 Code graph analysis (4)
src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java (2)
src/test/java/com/umc/product/support/RestDocsConfig.java (2)
  • TestConfiguration (10-21)
  • Bean (13-20)
src/test/java/com/umc/product/UmcProductApplicationTests.java (2)
  • SpringBootTest (6-12)
  • Test (9-11)
src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java (1)
src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java (1)
  • RestController (10-21)
src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java (1)
src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java (1)
  • RestController (16-42)
src/main/java/com/umc/product/organization/domain/Gisu.java (3)
src/main/java/com/umc/product/organization/domain/CentralOrganization.java (1)
  • Entity (18-49)
src/main/java/com/umc/product/organization/domain/Chapter.java (1)
  • Entity (18-48)
src/main/java/com/umc/product/organization/domain/StudyGroup.java (1)
  • Entity (22-146)
🔇 Additional comments (13)
src/docs/asciidoc/api/gisu/gisu.adoc (1)

1-26: LGTM!

기수 API 문서가 CRUD 작업과 현재 기수 설정 기능을 잘 정리하고 있습니다. REST Docs 구조를 올바르게 따르고 있습니다.

src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuListResponse.java (1)

6-11: 잘 구현된 Response DTO입니다!

  • record 타입을 사용하여 불변성을 보장합니다
  • from() 정적 팩토리 메서드로 도메인 객체에서 변환합니다
  • 스트림을 활용한 효율적인 리스트 변환을 수행합니다

Response DTO 패턴이 올바르게 적용되었습니다.

src/main/java/com/umc/product/organization/application/port/in/query/dto/GisuInfo.java (1)

5-6: Info DTO가 올바르게 정의되었습니다!

  • {Domain}Info 네이밍 패턴을 따릅니다
  • 불변 record 타입으로 구현되어 있습니다
  • 쿼리 결과를 전달하는 데이터 운반 객체로 적합합니다
src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolListItemInfo.java (1)

5-14: Info DTO가 잘 설계되었습니다!

  • 불변 record 타입으로 쿼리 결과를 안전하게 전달합니다
  • 여러 필드를 가독성 있게 멀티라인으로 포맷했습니다
  • School과 Chapter 정보를 결합한 리스트 항목 DTO로 적합합니다
src/test/java/com/umc/product/organization/adapter/in/web/GisuQueryControllerTest.java (1)

19-44: 테스트 코드가 가이드라인을 잘 준수하고 있습니다.

  • 한글 메서드명으로 명확한 의도 표현 ✓
  • Given-When-Then 구조가 명확하게 구분됨 ✓
  • REST Docs를 통한 API 문서화 ✓
  • Mock 설정과 검증이 적절함 ✓
src/main/java/com/umc/product/organization/adapter/in/web/dto/response/GisuResponse.java (1)

6-9: 잘 구현되었습니다!

Response DTO가 헥사고날 아키텍처 가이드라인을 올바르게 따르고 있습니다:

  1. record 타입 사용으로 불변성 보장
  2. from() 정적 팩토리 메서드로 도메인 Info 객체 변환
  3. ✓ 올바른 패키지 위치 (adapter/in/web/dto/response)
  4. ✓ 명확한 필드 매핑
src/main/java/com/umc/product/organization/domain/Gisu.java (1)

62-64: isInPeriod 메서드의 의도를 명확히 하는 문서화를 추가하세요.

현재 로직은 [startAt, endAt) 범위(시작 포함, 종료 제외)를 검사하는데, 이러한 경계 조건이 비즈니스 요구사항과 일치하는지 확인이 필요합니다. 특히 endAt 시각에는 기수가 종료된 것으로 처리되는지 명확하지 않습니다.

메서드에 JavaDoc을 추가하여 경계 조건을 명확히 하고, 비즈니스 요구사항과 일치하는지 확인하세요:

/**
 * 주어진 시각이 기수 기간에 포함되는지 확인합니다.
 * 
 * @param now 확인할 시각
 * @return 기수 기간 내 포함 여부 (시작일 포함, 종료일 제외)
 */
public boolean isInPeriod(LocalDateTime now) {
    return (now.isEqual(startAt) || now.isAfter(startAt)) && now.isBefore(endAt);
}

특히 endAt을 제외하는 것이 올바른 비즈니스 로직인지 검증이 필요합니다. 종료일도 기수 기간에 포함되어야 한다면 now.isBefore(endAt) 대신 now.isBefore(endAt) || now.isEqual(endAt) 또는 !now.isAfter(endAt)를 사용해야 합니다.

src/docs/asciidoc/api/school/school.adoc (1)

1-31: 문서 구조가 잘 정리되어 있습니다.

School API에 대한 6개 operation(목록 조회, 상세 조회, 등록, 삭제, 일괄 삭제, 수정)이 일관된 구조로 문서화되어 있습니다. 각 operation이 테스트 케이스와 올바르게 매핑되어 있어 API 문서 생성이 원활하게 이루어질 것으로 보입니다.

src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java (1)

78-92: 잘 구조화된 테스트입니다!

이 테스트는 Given-When-Then 구조가 명확하게 분리되어 있어 가독성이 좋습니다.

src/test/java/com/umc/product/support/DocumentationTest.java (4)

5-22: import 구문이 올바르게 구성되었습니다.

Organization 도메인의 컨트롤러 및 유스케이스 포트를 적절히 임포트하였고, Spring Boot 3.4+ 버전의 MockitoBean 위치를 올바르게 사용하고 있습니다.


49-65: MockitoBean을 올바르게 사용하여 의존성을 모킹했습니다.

@WebMvcTest 환경에서 @MockitoBean을 사용한 것이 적절하며, CQRS 패턴에 따라 커맨드(Manage) 및 쿼리(Get) 유스케이스를 분리하여 모킹한 구조가 명확합니다. protected 접근 제어자로 하위 테스트 클래스에서 접근 가능하도록 한 점도 좋습니다.

참고: 이 클래스는 문서화 테스트를 위한 베이스 클래스이므로, 학습된 가이드라인(ExtendWith(MockitoExtension.class) 사용)은 application/adapter 디렉토리의 순수 단위 테스트에 적용되며, 이 파일에는 해당하지 않습니다.


25-31: @WebMvcTest 컨트롤러 목록이 완전하게 정의되었습니다.

Organization 도메인의 모든 컨트롤러가 명시적으로 포함되어 있습니다. organization/adapter/in/web 디렉토리의 5개 Controller 클래스(SchoolController, SchoolQueryController, ChapterQueryController, GisuController, GisuQueryController)가 모두 @WebMvcTest에 나열되어 있어 슬라이스 테스트 범위가 정확하게 정의되었습니다.


35-35: @WebMvcTest 슬라이스 테스트에서 필터 비활성화는 적절합니다.

addFilters = false 설정은 @WebMvcTest를 통한 컨트롤러 슬라이스 테스트에서 표준 관행입니다. 이 테스트는 REST 문서화 목적이며, 보안 필터(JwtAuthenticationFilter 등)와 JwtTokenProvider@MockitoBean으로 적절히 모킹되어 있어 보안 관련 의존성이 격리되어 있습니다. 별도의 통합 테스트(@SpringBootTest)에서 실제 보안 동작을 검증하는 구조를 권장합니다.

Comment on lines +6 to +8
public record DeleteSchoolsRequest(
@NotEmpty List<Long> schoolIds
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

toCommand() 변환 메서드가 누락되었습니다.

코딩 가이드라인에 따르면 Request DTO는 toCommand() 메서드를 포함하여 command 레코드로 변환해야 합니다. 현재는 컨트롤러에서 request.schoolIds()를 직접 전달하고 있어 계층 간 결합도가 높아집니다.

♻️ toCommand() 메서드 추가 제안

먼저 command 레코드를 생성합니다:

// DeleteSchoolsCommand.java in application/port/in/command/dto/
package com.umc.product.organization.application.port.in.command.dto;

import java.util.List;

public record DeleteSchoolsCommand(
        List<Long> schoolIds
) {
}

그 다음 Request DTO에 변환 메서드를 추가합니다:

 package com.umc.product.organization.adapter.in.web.dto.request;

+import com.umc.product.organization.application.port.in.command.dto.DeleteSchoolsCommand;
 import jakarta.validation.constraints.NotEmpty;
 import java.util.List;

 public record DeleteSchoolsRequest(
         @NotEmpty List<Long> schoolIds
 ) {
+    public DeleteSchoolsCommand toCommand() {
+        return new DeleteSchoolsCommand(schoolIds);
+    }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
around lines 6 - 8, DeleteSchoolsRequest에 toCommand() 변환 메서드가 빠져 있어 컨트롤러가
request.schoolIds()를 직접 사용해 계층 결합도가 높습니다; 추가로 DeleteSchoolsCommand 레코드(예:
application.port.in.command.dto.DeleteSchoolsCommand)를 생성한 뒤
DeleteSchoolsRequest에 public DeleteSchoolsCommand toCommand() 메서드를 구현하여 내부
List<Long> schoolIds 값을 새 DeleteSchoolsCommand 인스턴스로 변환하도록 하고, 컨트롤러에서는
request.schoolIds() 대신 request.toCommand()를 전달하도록 수정하세요.

import java.util.List;

public record DeleteSchoolsRequest(
@NotEmpty List<Long> schoolIds
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

리스트 내 null 요소 검증이 누락되었습니다.

@NotEmpty는 리스트가 비어있지 않음만 검증하고, 리스트 내부의 Long 값이 null인지는 검증하지 않습니다. 이로 인해 null ID가 포함된 요청이 서비스 계층까지 전달될 수 있습니다.

✅ 검증 강화 제안
 import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
 import java.util.List;

 public record DeleteSchoolsRequest(
-        @NotEmpty List<Long> schoolIds
+        @NotEmpty List<@NotNull Long> schoolIds
 ) {
 }
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/DeleteSchoolsRequest.java
at line 7, The DeleteSchoolsRequest currently uses @NotEmpty on List<Long>
schoolIds which only checks the list is non-empty but not that individual Long
elements are non-null; update the field to List<@NotNull Long> schoolIds
(keeping @NotEmpty on the list) so element-level nulls are validated, add the
appropriate import for javax.validation.constraints.NotNull (or jakarta
equivalent) and ensure your validator is configured to validate container
element constraints.

Comment on lines +5 to +8
public record SchoolListRequest(
String keyword,
Long chapterId
) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

입력 검증 어노테이션이 누락되었습니다.

코딩 가이드라인에 따르면, Request DTO는 적절한 validation 어노테이션(@Valid, @NotNull, @NotBlank 등)을 포함해야 합니다. 쿼리 파라미터가 선택적(optional)이더라도, null 여부나 형식 검증이 필요한 경우 명시적으로 표현하는 것이 좋습니다.

✅ 검증 어노테이션 추가 제안
 package com.umc.product.organization.adapter.in.web.dto.request;
 
 import com.umc.product.organization.application.port.in.query.dto.SchoolSearchCondition;
+import jakarta.validation.constraints.Size;
 
 public record SchoolListRequest(
+        @Size(max = 100, message = "검색어는 100자를 초과할 수 없습니다")
         String keyword,
         Long chapterId
 ) {

참고: keywordchapterId가 모두 선택적 파라미터라면, 이를 명시적으로 문서화하거나 컨트롤러에서 @RequestParam(required = false)로 표현하는 것도 고려하세요.

코딩 가이드라인 기반 지적 사항

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/SchoolListRequest.java
around lines 5 - 8, The SchoolListRequest record is missing validation
annotations for its fields; add appropriate javax.validation constraints to the
record components (e.g., annotate keyword with @NotBlank or @Nullable/@Size if
optional, and chapterId with @NotNull or @Positive if it must be
present/positive) and ensure the controller maps optional query params with
@RequestParam(required = false) when applicable; update imports for
javax.validation (or jakarta.validation) annotations and adjust controller
parameter handling to match the DTO's optional/required semantics.

Comment on lines +7 to +10
public record UpdateGisuRequest(@NotNull LocalDate startAt, @NotNull LocalDate endAt) {
public UpdateGisuCommand toCommand(Long gisuId) {
return new UpdateGisuCommand(gisuId, startAt, endAt);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

요청 레벨에서 날짜 범위 검증을 추가하는 것을 고려하세요.

현재 startAtendAt의 순서 검증이 도메인 레벨(Gisu 엔티티)에서만 수행됩니다. 클라이언트에게 더 빠른 피드백을 제공하기 위해 요청 DTO 레벨에서도 커스텀 validation을 추가하는 것을 권장합니다.

♻️ 제안하는 개선안

커스텀 validation 어노테이션을 생성하거나, record에 검증 로직을 추가할 수 있습니다:

public record UpdateGisuRequest(@NotNull LocalDate startAt, @NotNull LocalDate endAt) {
    
    public UpdateGisuRequest {
        if (startAt != null && endAt != null && !startAt.isBefore(endAt)) {
            throw new IllegalArgumentException("시작일은 종료일보다 이전이어야 합니다.");
        }
    }
    
    public UpdateGisuCommand toCommand(Long gisuId) {
        return new UpdateGisuCommand(gisuId, startAt, endAt);
    }
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
around lines 7 - 10, The request DTO UpdateGisuRequest currently lacks
validation that startAt is before endAt; add validation at the DTO level by
implementing a compact constructor (record canonical constructor) or a custom
validation annotation on UpdateGisuRequest to check that startAt and endAt are
non-null and startAt.isBefore(endAt) (or startAt.isEqual(endAt) if equality
allowed) and throw a descriptive exception (e.g., IllegalArgumentException or a
ConstraintViolation) when the condition fails so clients get immediate feedback
before toCommand is used.

Comment on lines +25 to +87
@Test
void 신규_기수를_추가한다() throws Exception {
// given
CreateGisuRequest request = new CreateGisuRequest(9L, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 31));

given(manageGisuUseCase.register(any())).willReturn(1L);

// when
ResultActions result = mockMvc.perform(
post("/api/v1/admin/gisu").content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON));

// then
result.andExpect(status().isOk()).andDo(restDocsHandler.document(
requestFields(fieldWithPath("number").type(JsonFieldType.STRING).description("기수 번호"),
fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"),
fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시"))));
}

@Test
void 기수_정보를_수정한다() throws Exception {
// given
Long gisuId = 1L;
UpdateGisuRequest request = new UpdateGisuRequest(LocalDate.of(2025, 3, 1), LocalDate.of(2025, 9, 30));

// when
ResultActions result = mockMvc.perform(
patch("/api/v1/admin/gisu/{gisuId}", gisuId).content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON));

// then
result.andExpect(status().isOk())
.andDo(restDocsHandler.document(pathParameters(parameterWithName("gisuId").description("기수 ID")),
requestFields(fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"),
fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시"))));
}

@Test
void 기수를_삭제한다() throws Exception {
// given
Long gisuId = 1L;

// when
ResultActions result = mockMvc.perform(delete("/api/v1/admin/gisu/{gisuId}", gisuId));

// then
result.andExpect(status().isOk())
.andDo(restDocsHandler.document(pathParameters(parameterWithName("gisuId").description("기수 ID"))));
}

@Test
void 현재_기수를_설정한다() throws Exception {
// given
Long gisuId = 3L;

// when
ResultActions result = mockMvc.perform(post("/api/v1/admin/gisu/{gisuId}/current", gisuId));

// then
result.andExpect(status().isOk()).andDo(restDocsHandler.document(
pathParameters(parameterWithName("gisuId").description("현재 기수로 설정할 기수 ID"))));
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

테스트 커버리지 강화 권장

현재 Happy Path만 테스트하고 있습니다. 다음 시나리오에 대한 테스트 추가를 권장합니다:

  1. 입력 검증 실패 케이스

    • 필수 필드 누락 (null 값)
    • 날짜 범위 검증 (endAt < startAt)
    • 중복된 기수 번호
  2. 예외 케이스

    • 존재하지 않는 기수 ID로 수정/삭제 시도
    • 이미 활성화된 기수가 있을 때 다른 기수 활성화 시도

As per coding guidelines, boundary value tests and exception case tests should be included.

💡 예외 케이스 테스트 예시
@Test
void 기수_번호가_null이면_예외가_발생한다() throws Exception {
    // given
    CreateGisuRequest request = new CreateGisuRequest(null, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 31));
    
    // when & then
    mockMvc.perform(post("/api/v1/admin/gisu")
            .content(objectMapper.writeValueAsString(request))
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isBadRequest());
}

@Test
void 종료일이_시작일보다_빠르면_예외가_발생한다() throws Exception {
    // given
    CreateGisuRequest request = new CreateGisuRequest(9L, LocalDate.of(2025, 8, 31), LocalDate.of(2025, 3, 1));
    
    // when & then
    mockMvc.perform(post("/api/v1/admin/gisu")
            .content(objectMapper.writeValueAsString(request))
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isBadRequest());
}

Comment on lines +26 to +42
void 신규_기수를_추가한다() throws Exception {
// given
CreateGisuRequest request = new CreateGisuRequest(9L, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 31));

given(manageGisuUseCase.register(any())).willReturn(1L);

// when
ResultActions result = mockMvc.perform(
post("/api/v1/admin/gisu").content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON));

// then
result.andExpect(status().isOk()).andDo(restDocsHandler.document(
requestFields(fieldWithPath("number").type(JsonFieldType.STRING).description("기수 번호"),
fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"),
fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시"))));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

응답 본문 검증 누락

Line 30에서 manageGisuUseCase.register()1L을 반환하지만, 응답 본문에 대한 어떠한 검증도 수행하지 않습니다.

생성된 리소스의 ID가 응답에 포함되는지, 그리고 올바른 값인지 확인하는 검증을 추가해야 합니다.

♻️ 응답 검증 추가 예시
         // then
-        result.andExpect(status().isOk()).andDo(restDocsHandler.document(
+        result.andExpect(status().isOk())
+                .andExpect(jsonPath("$.data").value(1L))
+                .andDo(restDocsHandler.document(
                 requestFields(fieldWithPath("number").type(JsonFieldType.NUMBER).description("기수 번호"),
                         fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"),
                         fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시"))));
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java
around lines 26 - 42, The test 신규_기수를_추가한다 currently stubs
manageGisuUseCase.register to return 1L but never asserts the response body;
update the test to verify the controller returns the created resource ID (1) in
the response. After performing mockMvc.perform (ResultActions result), add
assertions on the response content (e.g., jsonPath or content) to check the ID
field equals 1 and the status remains OK, referencing the existing ResultActions
result, mockMvc.perform call, and the manageGisuUseCase.register stub so the
test validates the returned ID.

Comment on lines +38 to +41
result.andExpect(status().isOk()).andDo(restDocsHandler.document(
requestFields(fieldWithPath("number").type(JsonFieldType.STRING).description("기수 번호"),
fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"),
fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시"))));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

REST Docs 필드 타입 불일치

number 필드의 타입이 실제 요청 객체(CreateGisuRequest)에서는 Long인데, REST Docs에서는 JsonFieldType.STRING으로 문서화되어 있습니다.

Line 28에서 CreateGisuRequest의 생성자를 보면 첫 번째 파라미터 9LLong 타입입니다. 문서와 실제 타입이 일치해야 합니다.

🔧 타입 수정안
-                requestFields(fieldWithPath("number").type(JsonFieldType.STRING).description("기수 번호"),
+                requestFields(fieldWithPath("number").type(JsonFieldType.NUMBER).description("기수 번호"),
                         fieldWithPath("startAt").type(JsonFieldType.STRING).description("기수 시작일시"),
                         fieldWithPath("endAt").type(JsonFieldType.STRING).description("기수 종료일시"))));
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java
around lines 38 - 41, Test documents the "number" field as JsonFieldType.STRING
but the actual request object CreateGisuRequest uses a Long for the first param
(see the 9L in its constructor); update the REST Docs assertion in
GisuControllerTest (the call to
result.andExpect(...).andDo(restDocsHandler.document(...)) and specifically the
requestFields/fieldWithPath("number")) to use JsonFieldType.NUMBER so the
documented type matches the Long used by CreateGisuRequest.

class SchoolQueryControllerTest extends DocumentationTest {

@Test
void 학교_목록을_조회합니다() throws Exception {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

테스트 메서드명을 더 구체적인 패턴으로 개선하는 것을 고려하세요.

현재 테스트 메서드명이 단순히 동작만 설명하고 있습니다. 코딩 가이드라인에 따르면 {behavior}_{condition}_{result} 패턴을 따라 조건과 결과를 명시하는 것이 권장됩니다.

예시:

  • 학교_목록을_조회합니다유효한_파라미터로_학교_목록_조회시_성공
  • 학교_상세정보를_조회합니다존재하는_학교ID로_상세조회시_성공

As per coding guidelines, test method naming should follow {behavior}{condition}{result} pattern for clarity.

Also applies to: 87-87

🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
at line 28, The test method 이름 학교_목록을_조회합니다 in SchoolQueryControllerTest is too
generic; rename it to follow the {behavior}_{condition}_{result} pattern (e.g.,
유효한_파라미터로_학교_목록_조회시_성공) and apply the same pattern to the other test at the same
file (the method at line ~87, currently 학교_상세정보를_조회합니다) so names clearly express
behavior, condition, and expected result; update any references to these method
names (annotations, test runner) so the tests still execute.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 42

🤖 Fix all issues with AI agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/ChapterController.java:
- Around line 19-22: The controller currently returns Long directly from
createChapter; update createChapter to wrap the result in the standard
ApiResponse<T> wrapper (e.g., ApiResponse<Long>) before returning. Call
manageChapterUseCase.create(request.toCommand()), capture the Long id, and
return ApiResponse.success(id) (or the project's equivalent static factory) so
the response includes success/data/error fields; ensure method signature changes
from public Long createChapter(...) to public ApiResponse<Long>
createChapter(...), and adjust imports to reference ApiResponse.
- Around line 12-15: The ChapterController class is missing OpenAPI annotations;
add a @Tag annotation to the ChapterController class and add @Operation
annotations to each public endpoint method (e.g., methods in ChapterController
like list/create/update/delete handlers) with concise summary and description
values per the coding guideline so all public APIs are documented for Swagger.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java:
- Around line 7-10: CreateGisuRequest currently maps LocalDate fields directly
into CreateGisuCommand but Gisu entity requires LocalDateTime; update the
conversion so that before building the Gisu (e.g., where CreateGisuCommand is
consumed by Gisu.builder()), convert the command's startAt LocalDate to a
LocalDateTime at 00:00:00 and endAt LocalDate to LocalDateTime at 23:59:59 (or
another clearly defined policy) so the CreateGisuCommand -> Gisu.builder() flow
provides LocalDateTime values for the entity's start/end fields.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java:
- Around line 7-11: UpdateGisuRequest only checks @NotNull but lacks business
validation that startAt is before or equal to endAt; add a date-range validation
either by (A) creating a class-level custom constraint (e.g., @ValidDateRange
with a DateRangeValidator) and annotating the record UpdateGisuRequest so the
validator checks startAt <= endAt, or (B) adding a compact constructor inside
UpdateGisuRequest that throws a validation exception if startAt.isAfter(endAt);
keep toCommand as-is but ensure the record cannot be instantiated with an
invalid range.

In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java:
- Around line 9-12: Rename the static factory method
SchoolListItemResponse.of(SchoolListItemInfo) to from(SchoolListItemInfo) while
preserving its parameters and return behavior; update the method declaration in
class SchoolListItemResponse and replace all usages/call sites that call
SchoolListItemResponse.of(...) to SchoolListItemResponse.from(...), ensuring the
parameter type remains SchoolListItemInfo and the constructed fields (schoolId,
schoolName, chapterId, chapterName, createdAt, isActive) are unchanged.

In
@src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java:
- Line 17: GisuController and GisuQueryController share the same base
@RequestMapping("/api/v1/admin/gisu"), which can confuse API consumers; either
consolidate the two controllers into one class (merge GisuController and
GisuQueryController) or explicitly separate their base paths by changing the
annotation on one or both controllers (e.g., update GisuController's
@RequestMapping to "/api/v1/admin/gisu/commands" and GisuQueryController to
"/api/v1/admin/gisu/queries") so endpoints are clearly distinguished.
- Line 17: The controller mapped at @RequestMapping("/api/v1/admin/gisu")
(GisuController) exposes admin operations without method-level authorization;
add method-level security annotations (e.g., @PreAuthorize("hasRole('ADMIN')")
or @RolesAllowed("ROLE_ADMIN")) either at the class level on GisuController or
on each sensitive handler method (createGisu, updateGisu, deleteGisu,
setCurrentGisu) so that only authorized admin users can invoke them; ensure the
chosen annotation type matches your SecurityConfig (@EnableMethodSecurity) setup
and import the correct annotation package.
- Around line 16-19: The GisuController class is missing the OpenAPI @Tag
annotation required for public API controllers; add an appropriate @Tag(...)
annotation above the class declaration on GisuController (the class annotated
with @RestController, @RequestMapping("/api/v1/admin/gisu"),
@RequiredArgsConstructor) with a concise name and description matching your API
documentation conventions so Swagger/OpenAPI picks up this controller in the
generated docs.

In
@src/main/java/com/umc/product/organization/adapter/in/web/GisuQueryController.java:
- Around line 17-20: The controller method getGisuList currently returns
GisuListResponse directly; change it to return ApiResponse<GisuListResponse> and
wrap the payload using your standard success wrapper (e.g.,
ApiResponse.success(...)) so the response includes success, data, and error
fields; update the method signature in GisuQueryController.getGisuList and
return ApiResponse.success(GisuListResponse.from(getGisuUseCase.getList())),
adding any necessary imports for ApiResponse.
- Around line 10-13: The controller GisuQueryController is missing OpenAPI
annotations; add a class-level @Tag(name = "Gisu", description = "...") on
GisuQueryController and annotate each public endpoint method (e.g., list/query
methods in GisuQueryController) with @Operation(summary = "...", description =
"...") so Swagger groups and documents them; also import
io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation and ensure descriptive summaries for
each operation per coding guidelines.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolController.java:
- Around line 39-42: The deleteSchools endpoint (deleteSchools method) is
missing the standard ApiResponse<T> wrapper and an @Operation annotation; update
the method signature to return ApiResponse<Void> (or ApiResponse<?> per project
convention), annotate it with @Operation (provide summary/description consistent
with other controllers), and wrap the call to
manageSchoolUseCase.deleteSchools(request.schoolIds()) in an ApiResponse success
response; ensure you import/use the same ApiResponse class and annotation
pattern used elsewhere (and keep the method parameter as @RequestBody @Valid
DeleteSchoolsRequest).
- Around line 29-32: The controller accepts a @PathVariable schoolId but never
passes it to the use case; change UpdateSchoolRequest.toCommand and the
UpdateSchoolCommand (or the use case signature) so the schoolId is included: add
a schoolId field to UpdateSchoolCommand and update
UpdateSchoolRequest.toCommand(Long schoolId) to return the command with that id,
then call
manageSchoolUseCase.updateSchool(updateSchoolRequest.toCommand(schoolId));
alternatively, update manageSchoolUseCase.updateSchool to accept (Long schoolId,
UpdateSchoolCommand) and pass the id from updateSchool.
- Around line 34-37: The deleteSchool controller method currently returns void
and lacks documentation; change its signature to return ApiResponse<Void> and
annotate it with @Operation (include a summary/description), call
manageSchoolUseCase.deleteSchool(schoolId) as before, then return an ApiResponse
success wrapper (e.g., ApiResponse.success(null) or equivalent) to conform to
the controller response contract; ensure the method name deleteSchool and the
use of manageSchoolUseCase.deleteSchool(Long) remain and add any necessary
imports for ApiResponse and @Operation.
- Around line 17-20: The SchoolController class is missing OpenAPI
documentation; add the @Tag annotation on the SchoolController class (e.g.,
@Tag(name = "Schools", description = "...")) and ensure each public endpoint
method in SchoolController has an @Operation annotation with summary/description
to satisfy the coding guideline for public API endpoints; update import
statements for io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation as needed.

In
@src/main/java/com/umc/product/organization/adapter/in/web/SchoolQueryController.java:
- Around line 17-20: Add OpenAPI annotations to the SchoolQueryController:
annotate the SchoolQueryController class with @Tag (e.g., @Tag(name = "School",
description = "School administration endpoints")) to document the controller,
and ensure each public endpoint method inside SchoolQueryController has an
@Operation with a summary/description and appropriate responses; import
io.swagger.v3.oas.annotations.tags.Tag and
io.swagger.v3.oas.annotations.Operation and apply them to the class and methods
respectively to satisfy the coding guideline.
- Around line 24-28: The getSchoolList method in SchoolQueryController returns
PageResponse directly and lacks API documentation; wrap the existing
PageResponse.of(...) result inside the standard ApiResponse<T> (i.e., return
ApiResponse.success(PageResponse.of(...), or the project's ApiResponse.factory)
so the endpoint conforms to ApiResponse<PageResponse<SchoolListItemResponse>>,
and add an @Operation annotation above getSchoolList with a brief summary and
description (use the same wording convention as other controllers) to document
the endpoint; adjust the method return type to
ApiResponse<PageResponse<SchoolListItemResponse>> and keep the call to
getSchoolUseCase.getList(request.toCondition(), pageable) unchanged.
- Around line 30-36: The getSchoolDetail method currently returns
SchoolDetailResponse directly; change its signature to return
ApiResponse<SchoolDetailResponse>, annotate the controller method with
@Operation (provide a concise summary and description), wrap the
SchoolDetailResponse.of(schoolInfo) result in ApiResponse.success(...) (or the
project's ApiResponse factory), and add any necessary imports for ApiResponse
and @Operation so the method becomes documented and consistent with the
project's response wrapper convention.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateChapterCommand.java:
- Around line 3-4: CreateChapterCommand's record components lack validation
annotations so nulls can flow to the service; annotate the record components in
the header of CreateChapterCommand (the gisuId and name components) with
appropriate javax.validation annotations (e.g., @NotNull on gisuId and @NotNull
or @NotBlank on name) and add the corresponding imports so the validation
framework can enforce them when the command is used.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java:
- Around line 5-6: The CreateGisuCommand record is missing @NotNull validation
on its components; update the record declaration (CreateGisuCommand) to annotate
each component—number, startAt, endAt—with @NotNull and add the corresponding
import (e.g., javax.validation.constraints.NotNull) so the record becomes
immutable and validated at binding time.

In
@src/main/java/com/umc/product/organization/application/port/in/command/dto/UpdateGisuCommand.java:
- Around line 5-6: Update UpdateGisuCommand to include field-level validation
annotations and a conversion/validation flow: add @NotNull (or
jakarta.validation.constraints.NotNull) on gisuId, startAt, and endAt, implement
a compact constructor or static factory in the record that checks
startAt.isBefore(endAt) (or !startAt.isAfter(endAt)) and throws an
IllegalArgumentException (or your app’s ValidationException) when the date order
is invalid, and add a toEntity() method (e.g., toEntity() or toGisu()) that
transforms the command into the domain entity (call your Gisu constructor or
factory like Gisu.of(...)) so callers receive a validated, immutable entity
instance.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageChapterUseCase.java:
- Around line 5-8: The interface ManageChapterUseCase exposes only a single
create() method which violates SRP and the naming convention; rename the
interface ManageChapterUseCase to CreateChapterUseCase and update all references
(e.g., the create() declaration stays the same, injection points like the
ChapterController field previously typed as ManageChapterUseCase should be
changed to CreateChapterUseCase and renamed to createChapterUseCase), and when
adding other actions later create separate interfaces (UpdateChapterUseCase,
DeleteChapterUseCase) rather than expanding this interface.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java:
- Around line 6-15: The ManageGisuUseCase interface groups four
responsibilities; split it into four single-responsibility interfaces: create
RegisterGisuUseCase with Long register(CreateGisuCommand), UpdateGisuUseCase
with void updateGisu(UpdateGisuCommand), DeleteGisuUseCase with void
deleteGisu(Long gisuId), and SetCurrentGisuUseCase with void setCurrentGisu(Long
gisuId); update DTO imports to use the existing command DTOs (CreateGisuCommand,
UpdateGisuCommand), remove or deprecate ManageGisuUseCase, and update any
service/adapter classes that currently implement ManageGisuUseCase to implement
the specific new interfaces they provide.

In
@src/main/java/com/umc/product/organization/application/port/in/command/ManageSchoolUseCase.java:
- Around line 7-16: ManageSchoolUseCase currently groups four responsibilities
and violates SRP/ISP; split it into one-use-case interfaces: create an interface
(e.g., RegisterSchoolUseCase) exposing register(CreateSchoolCommand),
UpdateSchoolUseCase exposing updateSchool(UpdateSchoolCommand),
DeleteSchoolUseCase exposing deleteSchool(Long schoolId), and
DeleteSchoolsUseCase (or BulkDeleteSchoolUseCase) exposing
deleteSchools(List<Long> schoolIds); update service implementations to implement
only the interfaces they need and replace references to ManageSchoolUseCase with
the specific new interfaces where used (look for ManageSchoolUseCase, register,
updateSchool, deleteSchool, deleteSchools, CreateSchoolCommand,
UpdateSchoolCommand to find all usages).

In
@src/main/java/com/umc/product/organization/application/port/in/query/dto/SchoolSearchCondition.java:
- Around line 3-6: The SchoolSearchCondition record lacks
validation/documentation for its two components; decide whether keyword and
chapterId are optional or mandatory and update accordingly: if both are
optional, add Javadoc/comments to the record and annotate fields with @Nullable
(or document as optional) and, for keyword, optionally add @Size(max=...) to
constrain length; if chapterId must be present, annotate the chapterId component
with @NotNull and add Javadoc stating it is required; ensure you import
javax.validation.constraints annotations (e.g., @NotNull, @Size) or a @Nullable
from your codebase and place annotations on the record components in
SchoolSearchCondition so validation frameworks pick them up.

In
@src/main/java/com/umc/product/organization/application/port/in/query/GetSchoolUseCase.java:
- Around line 10-15: GetSchoolUseCase currently contains two responsibilities
(getList and getSchoolDetail); split it into two single-responsibility
interfaces: create GetSchoolListUseCase exposing Page<SchoolListItemInfo>
getList(SchoolSearchCondition, Pageable) and GetSchoolDetailUseCase exposing
SchoolInfo getSchoolDetail(Long), then update any implementations or injection
sites that reference GetSchoolUseCase to use the appropriate new interface
(e.g., classes implementing getList should implement GetSchoolListUseCase, those
implementing getSchoolDetail should implement GetSchoolDetailUseCase).

In
@src/main/java/com/umc/product/organization/application/port/service/command/SchoolService.java:
- Around line 11-25: The service methods register(CreateSchoolCommand),
updateSchool(UpdateSchoolCommand), deleteSchool(Long) and
deleteSchools(java.util.List<Long>) are empty; inject and use the outbound ports
(SaveSchoolPort, LoadSchoolPort) via constructor, perform input validation,
load/create/modify the domain entity (School, and related Chapter if
applicable), call the appropriate port methods to persist or remove entities,
and throw domain-specific exceptions on validation/load failures; ensure
updateSchool loads the existing School via LoadSchoolPort, applies changes, then
calls SaveSchoolPort.save(or equivalent), and implement deleteSchools to
batch-validate ids and call the delete/remove port within a transactional
boundary.
- Around line 8-9: The SchoolService class (implements ManageSchoolUseCase) must
be renamed to SchoolCommandService and annotated with @Transactional; update the
class declaration from SchoolService to SchoolCommandService and add the
@Transactional annotation above the class, keeping the implements
ManageSchoolUseCase unchanged, and adjust any references/usages of SchoolService
elsewhere (constructor injection, tests, configuration) to the new
SchoolCommandService name.
- Line 1: Service class SchoolService is placed in the wrong package
(application.port.service.command) violating hexagonal architecture; move its
package to application.service.command and update all references. Change the
package declaration in the SchoolService source to application.service.command,
relocate the file into the corresponding directory, update any imports/usages
(tests, configuration, DI wiring) that reference the old package, and ensure
build/packaging configs reflect the new path so compilation and dependency
injection for SchoolService continue to work.

In @src/main/java/com/umc/product/organization/domain/Gisu.java:
- Around line 25-31: The Gisu entity's DB columns lack JPA column constraints;
update the fields in class Gisu to enforce non-nullability and uniqueness:
annotate number with @Column(nullable = false, unique = true) to prevent nulls
and duplicate 기수 numbers, and annotate startAt and endAt (and isActive if you
want explicit DB-level constraint) with @Column(nullable = false); optionally
add javax.validation @NotNull on the same fields for bean validation.
- Line 25: The Gisu entity is missing null validation for the Long number field;
update the validate method in class Gisu to check that number is not null (throw
IllegalArgumentException or domain-specific exception with a clear message) and
ensure the constructor that sets this.number calls that validate method (or
adjust the validate signature if you prefer passing number into validate,
mirroring patterns used in Chapter/CentralOrganization) so a Gisu cannot be
created with a null number.

In
@src/test/java/com/umc/product/organization/adapter/in/web/ChapterControllerTest.java:
- Around line 33-34: The REST Docs field type for gisuId is incorrect: in the
requestFields(...) call where fieldWithPath("gisuId") is currently documented as
JsonFieldType.STRING, change it to JsonFieldType.NUMBER so it matches the actual
request payload (Long/number); update any other fieldWithPath(...) entries in
the same test (e.g., "name") only if their JsonFieldType does not match the real
serialized types.

In
@src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java:
- Around line 25-42: The REST Docs field type for "number" in the test method
신규_기수를_추가한다 (class GisuControllerTest) is incorrect: update the documentation
assertion to match the CreateGisuRequest which uses a Long (9L). Replace
fieldWithPath("number").type(JsonFieldType.STRING) with
fieldWithPath("number").type(JsonFieldType.NUMBER) in the ResultActions
assertion so the documented type matches the CreateGisuRequest payload.

In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java:
- Around line 78-91: Add exception-case tests to SchoolControllerTest to cover
boundary scenarios: create separate @Test methods (e.g.,
deleteNonExistentSchools, deleteWithEmptyList, deleteWithInvalidIds) that reuse
DeleteSchoolsRequest and mockMvc.perform(delete("/api/v1/admin/schools")...) but
set up the service/mocks to return the appropriate errors (e.g., throw
EntityNotFoundException for non-existent IDs, return 400 for invalid IDs, and
validate empty list behavior), then assert expected statuses (404 or 400) and
error response bodies and include restDocsHandler.document calls for
request/responseFields to document these error cases.
- Around line 24-40: The test method 총괄_신규학교를_추가한다() mixes Given/When/Then
steps; split them clearly by moving the request setup
(CreateSchoolRequest.builder...build()) into the Given section, performing the
action (mockMvc.perform(...) and assigning to ResultActions result) into the
When section, and placing all assertions
(result.andExpect(...).andDo(restDocsHandler.document(...))) into the Then
section; add brief comments or blank lines marking Given/When/Then and keep the
same symbols (CreateSchoolRequest, mockMvc.perform, ResultActions result,
result.andExpect, restDocsHandler.document) so the structure is explicit and
easy to read.
- Around line 42-61: The test is using the wrong DTO and has a fuzzy
Given-When-Then structure: replace CreateSchoolRequest with UpdateSchoolRequest
when building the request payload (change the variable type and builder call
where CreateSchoolRequest.builder() is used) and clearly separate the phases by
adding explicit given/when/then comments or blocks around the setup (schoolId
and request creation), the action (mockMvc.perform(...) assigned to result) and
the assertions (result.andExpect... andDo...), keeping requestFields unchanged
but matching the UpdateSchoolRequest fields.

In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java:
- Around line 76-79: In SchoolQueryControllerTest update the API docs for
pagination: change the four field descriptors that currently use
JsonFieldType.STRING—specifically the fieldWithPath entries for "result.page",
"result.size", "result.totalElements", and "result.totalPages"—to use
JsonFieldType.NUMBER so the documented types match the numeric values returned
by the Spring Data Page object; keep the descriptions the same and run the test
to ensure snippets regenerate correctly.
- Around line 104-108: The response field types for numeric IDs are documented
incorrectly: update the documented types for result.chapterId and
result.schoolId in SchoolQueryControllerTest (the fieldWithPath entries) from
JsonFieldType.STRING to JsonFieldType.NUMBER to match the Long values used when
creating SchoolInfo (e.g., 3L, 1L) so the generated API docs reflect numeric
IDs.
- Around line 70-75: In SchoolQueryControllerTest update the REST Docs field
types for numeric IDs: change the JsonFieldType for "result.content[].schoolId"
and "result.content[].chapterId" from JsonFieldType.STRING to
JsonFieldType.NUMBER so the documented types match the Long values used in the
test data and actual JSON serialization.

In @src/test/java/com/umc/product/support/DocumentationTest.java:
- Around line 27-34: The test class DocumentationTest currently annotates
WebMvcTest with six controllers (SchoolController, SchoolQueryController,
ChapterController, ChapterQueryController, GisuController, GisuQueryController),
causing unnecessary controller loading and slower, coupled tests; split the
tests into domain-specific test classes (e.g., SchoolDocumentationTest,
ChapterDocumentationTest, GisuDocumentationTest) each annotated with WebMvcTest
referencing only their domain's controllers, move shared configuration and
autowired beans (MockMvc, RestDocumentationResultHandler, ObjectMapper,
JwtTokenProvider) into an abstract BaseDocumentationTest, and keep
domain-specific @MockitoBean mocks (ManageSchoolUseCase, GetSchoolUseCase,
ManageChapterUseCase, GetChapterUseCase, ManageGisuUseCase, GetGisuUseCase) in
their respective domain test classes.

Comment on lines +12 to +15
@RestController
@RequestMapping("/api/v1/admin/chapters")
@RequiredArgsConstructor
public class ChapterController {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OpenAPI 문서화 어노테이션이 누락되었습니다.

Coding guideline에 따르면 모든 공개 API는 @Tag@Operation 어노테이션을 포함해야 합니다. 컨트롤러 클래스와 메서드에 Swagger 문서화를 추가하세요.

Based on coding guidelines, all public API endpoints must include OpenAPI/Swagger documentation with @tag and @operation annotations.

📝 OpenAPI 문서화 추가안
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 @RestController
 @RequestMapping("/api/v1/admin/chapters")
 @RequiredArgsConstructor
+@Tag(name = "Chapter", description = "지부 관리 API")
 public class ChapterController {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/ChapterController.java
around lines 12 - 15, The ChapterController class is missing OpenAPI
annotations; add a @Tag annotation to the ChapterController class and add
@Operation annotations to each public endpoint method (e.g., methods in
ChapterController like list/create/update/delete handlers) with concise summary
and description values per the coding guideline so all public APIs are
documented for Swagger.

Comment on lines +19 to +22
@PostMapping
public Long createChapter(@RequestBody @Valid CreateChapterRequest request) {
return manageChapterUseCase.create(request.toCommand());
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

API 응답 형식이 표준 ApiResponse 포맷을 따르지 않습니다.

Coding guideline에 따르면 모든 API 응답은 ApiResponse<T> 형식을 따라야 하며, success, data, error 필드를 포함해야 합니다. 현재 Long을 직접 반환하고 있어 일관성 있는 응답 구조를 제공하지 못합니다.

Based on coding guidelines, all API responses must follow the standard ApiResponse format.

♻️ 표준 응답 형식 적용안
+import com.umc.product.global.common.response.ApiResponse;
+import io.swagger.v3.oas.annotations.Operation;
+
     @PostMapping
-    public Long createChapter(@RequestBody @Valid CreateChapterRequest request) {
-        return manageChapterUseCase.create(request.toCommand());
+    @Operation(summary = "신규 지부 생성", description = "기수에 새로운 지부를 생성합니다.")
+    public ApiResponse<Long> createChapter(@RequestBody @Valid CreateChapterRequest request) {
+        Long chapterId = manageChapterUseCase.create(request.toCommand());
+        return ApiResponse.onSuccess(chapterId);
     }
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/ChapterController.java
around lines 19 - 22, The controller currently returns Long directly from
createChapter; update createChapter to wrap the result in the standard
ApiResponse<T> wrapper (e.g., ApiResponse<Long>) before returning. Call
manageChapterUseCase.create(request.toCommand()), capture the Long id, and
return ApiResponse.success(id) (or the project's equivalent static factory) so
the response includes success/data/error fields; ensure method signature changes
from public Long createChapter(...) to public ApiResponse<Long>
createChapter(...), and adjust imports to reference ApiResponse.

Comment on lines +7 to +10
public record CreateGisuRequest(@NotNull Long number, @NotNull LocalDate startAt, @NotNull LocalDate endAt) {
public CreateGisuCommand toCommand() {
return new CreateGisuCommand(number, startAt, endAt);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the file structure and locate CreateGisuCommand
find . -type f -name "*.java" | grep -i "creategisu" | head -20

Repository: UMC-PRODUCT/umc-product-server

Length of output: 268


🏁 Script executed:

# Also search for CreateGisuCommand definition
rg -l "CreateGisuCommand" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 358


🏁 Script executed:

# Find Gisu entity
rg -l "class Gisu|record Gisu" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 732


🏁 Script executed:

cat -n src/main/java/com/umc/product/organization/application/port/in/command/dto/CreateGisuCommand.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 300


🏁 Script executed:

cat -n src/main/java/com/umc/product/organization/domain/Gisu.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 2479


🏁 Script executed:

# Find the service implementation that uses CreateGisuCommand
rg -l "CreateGisuCommand" --type=java -g "!test"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 358


🏁 Script executed:

# Search for where Gisu.builder() is called with the command
rg -nP --type=java "Gisu\.builder\(\)" -A 10 -B 2

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

cat -n src/main/java/com/umc/product/organization/application/port/in/command/ManageGisuUseCase.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 629


🏁 Script executed:

# Search for service implementations of ManageGisuUseCase
rg -l "ManageGisuUseCase" --type=java -g "!test"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 245


🏁 Script executed:

# Find the service implementation class
rg "class.*implements.*ManageGisuUseCase" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Also search for "implements ManageGisuUseCase"
rg -n "implements ManageGisuUseCase" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Search for register method that takes CreateGisuCommand
rg -n "register\(CreateGisuCommand" --type=java -B 5 -A 15

Repository: UMC-PRODUCT/umc-product-server

Length of output: 1711


🏁 Script executed:

# Search for all java files in application/service directory
fd "\.java$" src/main/java/com/umc/product/organization/application -type f

Repository: UMC-PRODUCT/umc-product-server

Length of output: 244


🏁 Script executed:

# Find all java files in application directory
find src/main/java/com/umc/product/organization/application -name "*.java" | sort

Repository: UMC-PRODUCT/umc-product-server

Length of output: 3334


🏁 Script executed:

# Check the controller to see how it uses ManageGisuUseCase
cat -n src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 2004


🏁 Script executed:

# Search for where Gisu is created from CreateGisuCommand
rg -n "new Gisu|Gisu\.builder" --type=java -B 3 -A 8

Repository: UMC-PRODUCT/umc-product-server

Length of output: 3665


🏁 Script executed:

# Search for where Gisu entity is actually instantiated with builder
rg -n "\.builder\(\)" --type=java -A 10 | grep -A 10 -B 2 "Gisu"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Check if there's an adapter/out directory with repository
find src/main/java/com/umc/product/organization -type d -name "*adapter*" -o -name "*out*"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 188


🏁 Script executed:

# Search for ManageGisuPort implementation
rg -n "ManageGisuPort" --type=java -B 2 -A 15

Repository: UMC-PRODUCT/umc-product-server

Length of output: 969


🏁 Script executed:

# Find adapter/out directory structure
find src/main/java/com/umc/product/organization/adapter/out -name "*.java" | sort

Repository: UMC-PRODUCT/umc-product-server

Length of output: 163


🏁 Script executed:

# Also search for where register method is implemented
rg -n "register\(" --type=java -B 5 -A 15 | head -100

Repository: UMC-PRODUCT/umc-product-server

Length of output: 11784


🏁 Script executed:

# Find the service implementation files
find src/main/java/com/umc/product/organization/application -name "*Service.java" | sort

Repository: UMC-PRODUCT/umc-product-server

Length of output: 168


🏁 Script executed:

# Search specifically for GisuService
rg -n "class GisuService" --type=java -A 50

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Search for any class that implements ManageGisuUseCase
rg -n "implements ManageGisuUseCase" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Search for all service classes in the organization module
find src/main/java/com/umc/product/organization -name "*.java" -path "*/service/*"

Repository: UMC-PRODUCT/umc-product-server

Length of output: 168


🏁 Script executed:

# Also search for where CreateGisuCommand is used with command.
rg -n "command\.startAt\|command\.endAt" --type=java -B 5 -A 10

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Find all java files in the organization module
find src/main/java/com/umc/product/organization -name "*.java" | wc -l

Repository: UMC-PRODUCT/umc-product-server

Length of output: 76


🏁 Script executed:

# List all files
find src/main/java/com/umc/product/organization -name "*.java" | sort

Repository: UMC-PRODUCT/umc-product-server

Length of output: 5607


🏁 Script executed:

# Search for any file that mentions "register" and "CreateGisuCommand"
rg -n "register.*CreateGisuCommand\|CreateGisuCommand.*register" --type=java -B 10 -A 15

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Search in test files to understand how this is supposed to work
find src/test -name "*.java" | xargs grep -l "CreateGisuCommand" | head -5

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Check the adapter directory structure
find src/main/java/com/umc/product/organization/adapter -type f -name "*.java" | sort

Repository: UMC-PRODUCT/umc-product-server

Length of output: 1799


🏁 Script executed:

# Search across all modules for GisuService or any implementation
rg -n "class.*implements.*ManageGisuUseCase" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Search more broadly for where Gisu is created
rg -n "Gisu\(" --type=java -B 5 -A 3

Repository: UMC-PRODUCT/umc-product-server

Length of output: 5685


🏁 Script executed:

# Check if there's configuration or factory methods
rg -n "@Bean.*Gisu\|ManageGisuUseCase" --type=java -B 3 -A 10

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Search across entire repository for register implementation
rg -n "Long register\(CreateGisuCommand" --type=java -B 5 -A 20

Repository: UMC-PRODUCT/umc-product-server

Length of output: 1711


🏁 Script executed:

# Search for any class with "Gisu" and "implements"
rg -n "class.*Gisu.*implements" --type=java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 56


🏁 Script executed:

# Look for test files that show how this is used
cat -n src/test/java/com/umc/product/organization/adapter/in/web/GisuControllerTest.java

Repository: UMC-PRODUCT/umc-product-server

Length of output: 4560


타입 불일치: LocalDate vs LocalDateTime

CreateGisuCommandLocalDate를 사용하지만, Gisu 엔티티는 LocalDateTime을 요구합니다. 이는 커맨드에서 엔티티로의 변환 시 문제를 야기할 수 있습니다.

변환 로직이 명확하게 처리되어 있지 않으므로, CreateGisuCommandGisu.builder()에 전달하기 전에 LocalDateLocalDateTime으로 변환하는 명시적인 로직이 필요합니다. 예를 들어, 시작일은 00:00:00으로, 종료일은 23:59:59로 설정하는 등의 명확한 정책을 정의하고 구현해야 합니다.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/CreateGisuRequest.java
around lines 7 - 10, CreateGisuRequest currently maps LocalDate fields directly
into CreateGisuCommand but Gisu entity requires LocalDateTime; update the
conversion so that before building the Gisu (e.g., where CreateGisuCommand is
consumed by Gisu.builder()), convert the command's startAt LocalDate to a
LocalDateTime at 00:00:00 and endAt LocalDate to LocalDateTime at 23:59:59 (or
another clearly defined policy) so the CreateGisuCommand -> Gisu.builder() flow
provides LocalDateTime values for the entity's start/end fields.

Comment on lines +7 to +11
public record UpdateGisuRequest(@NotNull LocalDate startAt, @NotNull LocalDate endAt) {
public UpdateGisuCommand toCommand(Long gisuId) {
return new UpdateGisuCommand(gisuId, startAt, endAt);
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

날짜 범위 검증 로직이 누락되었습니다.

@NotNull 검증만 있고 startAtendAt보다 이전인지 확인하는 비즈니스 로직 검증이 없습니다. 이로 인해 잘못된 날짜 범위가 시스템에 입력될 수 있습니다.

✅ 제안하는 검증 로직 추가

커스텀 검증 애노테이션을 추가하거나, 컴팩트 생성자에서 검증하는 방법을 권장합니다:

방법 1: 커스텀 검증 애노테이션 (권장)

@ValidDateRange
public record UpdateGisuRequest(
    @NotNull LocalDate startAt, 
    @NotNull LocalDate endAt
) {
    public UpdateGisuCommand toCommand(Long gisuId) {
        return new UpdateGisuCommand(gisuId, startAt, endAt);
    }
}

그리고 별도로 @ValidDateRange 애노테이션과 validator 구현:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {
    String message() default "시작일은 종료일보다 이전이어야 합니다";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

방법 2: Record 컴팩트 생성자 (간단한 경우)

 public record UpdateGisuRequest(@NotNull LocalDate startAt, @NotNull LocalDate endAt) {
+    public UpdateGisuRequest {
+        if (startAt != null && endAt != null && startAt.isAfter(endAt)) {
+            throw new IllegalArgumentException("시작일은 종료일보다 이전이어야 합니다");
+        }
+    }
+
     public UpdateGisuCommand toCommand(Long gisuId) {
         return new UpdateGisuCommand(gisuId, startAt, endAt);
     }
 }

As per coding guidelines: Request DTOs must include proper validation for business rules, not just null checks.

🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/request/UpdateGisuRequest.java
around lines 7 - 11, UpdateGisuRequest only checks @NotNull but lacks business
validation that startAt is before or equal to endAt; add a date-range validation
either by (A) creating a class-level custom constraint (e.g., @ValidDateRange
with a DateRangeValidator) and annotating the record UpdateGisuRequest so the
validator checks startAt <= endAt, or (B) adding a compact constructor inside
UpdateGisuRequest that throws a validation exception if startAt.isAfter(endAt);
keep toCommand as-is but ensure the record cannot be instantiated with an
invalid range.

Comment on lines +9 to +12
public static SchoolListItemResponse of(SchoolListItemInfo summary) {
return new SchoolListItemResponse(summary.schoolId(), summary.schoolName(), summary.chapterId(),
summary.chapterName(), summary.createdAt(), summary.isActive());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

팩토리 메서드 이름을 from()으로 변경하세요.

Response DTO의 정적 팩토리 메서드는 of() 대신 from()을 사용해야 합니다. 이는 도메인 Info 객체로부터 변환한다는 의미를 명확히 전달합니다.

Based on learnings, Response DTO classes should provide static from() method for conversion from domain Info objects.

♻️ 제안하는 수정
-    public static SchoolListItemResponse of(SchoolListItemInfo summary) {
+    public static SchoolListItemResponse from(SchoolListItemInfo summary) {
         return new SchoolListItemResponse(summary.schoolId(), summary.schoolName(), summary.chapterId(),
                 summary.chapterName(), summary.createdAt(), summary.isActive());
     }
📝 Committable suggestion

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

Suggested change
public static SchoolListItemResponse of(SchoolListItemInfo summary) {
return new SchoolListItemResponse(summary.schoolId(), summary.schoolName(), summary.chapterId(),
summary.chapterName(), summary.createdAt(), summary.isActive());
}
public static SchoolListItemResponse from(SchoolListItemInfo summary) {
return new SchoolListItemResponse(summary.schoolId(), summary.schoolName(), summary.chapterId(),
summary.chapterName(), summary.createdAt(), summary.isActive());
}
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/dto/response/SchoolListItemResponse.java
around lines 9 - 12, Rename the static factory method
SchoolListItemResponse.of(SchoolListItemInfo) to from(SchoolListItemInfo) while
preserving its parameters and return behavior; update the method declaration in
class SchoolListItemResponse and replace all usages/call sites that call
SchoolListItemResponse.of(...) to SchoolListItemResponse.from(...), ensuring the
parameter type remains SchoolListItemInfo and the constructed fields (schoolId,
schoolName, chapterId, chapterName, createdAt, isActive) are unchanged.

Comment on lines +63 to +76
@Test
void 총괄_학교를_제거한다() throws Exception {
// given // when
Long schoolId = 1L;

ResultActions result = mockMvc.perform(delete("/api/v1/admin/schools/{schoolId}", schoolId));

// then
result.andExpect(status().isOk())
.andDo(restDocsHandler.document(pathParameters(parameterWithName("schoolId").description("학교 ID")

)));

}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Given-When-Then 구조 일관성 유지

다른 테스트들과 마찬가지로 Given-When-Then 주석이 불분명합니다. 일관된 구조를 유지해주세요.

♻️ 제안하는 개선안
     @Test
     void 총괄_학교를_제거한다() throws Exception {
-        // given // when
+        // given
         Long schoolId = 1L;
 
+        // when
         ResultActions result = mockMvc.perform(delete("/api/v1/admin/schools/{schoolId}", schoolId));
 
         // then
📝 Committable suggestion

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

Suggested change
@Test
void 총괄_학교를_제거한다() throws Exception {
// given // when
Long schoolId = 1L;
ResultActions result = mockMvc.perform(delete("/api/v1/admin/schools/{schoolId}", schoolId));
// then
result.andExpect(status().isOk())
.andDo(restDocsHandler.document(pathParameters(parameterWithName("schoolId").description("학교 ID")
)));
}
@Test
void 총괄_학교를_제거한다() throws Exception {
// given
Long schoolId = 1L;
// when
ResultActions result = mockMvc.perform(delete("/api/v1/admin/schools/{schoolId}", schoolId));
// then
result.andExpect(status().isOk())
.andDo(restDocsHandler.document(pathParameters(parameterWithName("schoolId").description("학교 ID")
)));
}

Comment on lines +78 to +91
@Test
void 총괄_학교를_일괄_삭제한다() throws Exception {
// given
DeleteSchoolsRequest request = new DeleteSchoolsRequest(List.of(1L, 2L, 3L));

// when
ResultActions result = mockMvc.perform(
delete("/api/v1/admin/schools").content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON));

// then
result.andExpect(status().isOk()).andDo(restDocsHandler.document(
requestFields(fieldWithPath("schoolIds").type(JsonFieldType.ARRAY).description("삭제할 학교 ID 목록"))));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

예외 케이스 테스트 추가 권장

현재 테스트는 성공 케이스만 다루고 있습니다. 다음과 같은 예외 케이스도 테스트하는 것이 좋습니다:

  • 존재하지 않는 학교 ID로 삭제 시도
  • 빈 리스트로 일괄 삭제 시도
  • 유효하지 않은 학교 ID가 포함된 경우

Based on coding guidelines, tests should include boundary value tests and exception case tests.

🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolControllerTest.java
around lines 78 - 91, Add exception-case tests to SchoolControllerTest to cover
boundary scenarios: create separate @Test methods (e.g.,
deleteNonExistentSchools, deleteWithEmptyList, deleteWithInvalidIds) that reuse
DeleteSchoolsRequest and mockMvc.perform(delete("/api/v1/admin/schools")...) but
set up the service/mocks to return the appropriate errors (e.g., throw
EntityNotFoundException for non-existent IDs, return 400 for invalid IDs, and
validate empty list behavior), then assert expected statuses (404 or 400) and
error response bodies and include restDocsHandler.document calls for
request/responseFields to document these error cases.

Comment on lines +70 to +75
fieldWithPath("result.content[].schoolId").type(JsonFieldType.STRING).description("학교 ID"),
fieldWithPath("result.content[].schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("result.content[].chapterId").type(JsonFieldType.STRING).description("지부 ID"),
fieldWithPath("result.content[].chapterName").type(JsonFieldType.STRING).description("지부 이름"),
fieldWithPath("result.content[].createdAt").type(JsonFieldType.STRING).description("등록일"),
fieldWithPath("result.content[].isActive").type(JsonFieldType.BOOLEAN).description("활성 상태"),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

REST Docs 필드 타입이 실제 데이터 타입과 불일치합니다.

schoolId, chapterIdLong 타입인데 JsonFieldType.STRING으로 문서화되고 있습니다. Lines 34-36의 테스트 데이터에서 1L, 2L, 3L과 같은 Long 값을 사용하고 있으므로 실제 JSON 응답에서도 숫자로 직렬화됩니다.

💚 타입 수정안
                                 fieldWithPath("result.content").type(JsonFieldType.ARRAY).description("학교 목록"),
-                                fieldWithPath("result.content[].schoolId").type(JsonFieldType.STRING).description("학교 ID"),
+                                fieldWithPath("result.content[].schoolId").type(JsonFieldType.NUMBER).description("학교 ID"),
                                 fieldWithPath("result.content[].schoolName").type(JsonFieldType.STRING).description("학교 이름"),
-                                fieldWithPath("result.content[].chapterId").type(JsonFieldType.STRING).description("지부 ID"),
+                                fieldWithPath("result.content[].chapterId").type(JsonFieldType.NUMBER).description("지부 ID"),
                                 fieldWithPath("result.content[].chapterName").type(JsonFieldType.STRING).description("지부 이름"),
                                 fieldWithPath("result.content[].createdAt").type(JsonFieldType.STRING).description("등록일"),
                                 fieldWithPath("result.content[].isActive").type(JsonFieldType.BOOLEAN).description("활성 상태"),
📝 Committable suggestion

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

Suggested change
fieldWithPath("result.content[].schoolId").type(JsonFieldType.STRING).description("학교 ID"),
fieldWithPath("result.content[].schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("result.content[].chapterId").type(JsonFieldType.STRING).description("지부 ID"),
fieldWithPath("result.content[].chapterName").type(JsonFieldType.STRING).description("지부 이름"),
fieldWithPath("result.content[].createdAt").type(JsonFieldType.STRING).description("등록일"),
fieldWithPath("result.content[].isActive").type(JsonFieldType.BOOLEAN).description("활성 상태"),
fieldWithPath("result.content[].schoolId").type(JsonFieldType.NUMBER).description("학교 ID"),
fieldWithPath("result.content[].schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("result.content[].chapterId").type(JsonFieldType.NUMBER).description("지부 ID"),
fieldWithPath("result.content[].chapterName").type(JsonFieldType.STRING).description("지부 이름"),
fieldWithPath("result.content[].createdAt").type(JsonFieldType.STRING).description("등록일"),
fieldWithPath("result.content[].isActive").type(JsonFieldType.BOOLEAN).description("활성 상태"),
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
around lines 70 - 75, In SchoolQueryControllerTest update the REST Docs field
types for numeric IDs: change the JsonFieldType for "result.content[].schoolId"
and "result.content[].chapterId" from JsonFieldType.STRING to
JsonFieldType.NUMBER so the documented types match the Long values used in the
test data and actual JSON serialization.

Comment on lines +76 to +79
fieldWithPath("result.page").type(JsonFieldType.STRING).description("현재 페이지 번호 (0부터 시작)"),
fieldWithPath("result.size").type(JsonFieldType.STRING).description("페이지 당 조회 수"),
fieldWithPath("result.totalElements").type(JsonFieldType.STRING).description("총 학교 수"),
fieldWithPath("result.totalPages").type(JsonFieldType.STRING).description("총 페이지 수"),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

페이지네이션 필드 타입이 잘못 문서화되었습니다.

page, size, totalElements, totalPages는 숫자 타입인데 JsonFieldType.STRING으로 문서화되고 있습니다. Spring Data의 Page 객체는 이들을 숫자로 반환하므로 JsonFieldType.NUMBER로 수정해야 합니다.

💚 타입 수정안
-                                fieldWithPath("result.page").type(JsonFieldType.STRING).description("현재 페이지 번호 (0부터 시작)"),
-                                fieldWithPath("result.size").type(JsonFieldType.STRING).description("페이지 당 조회 수"),
-                                fieldWithPath("result.totalElements").type(JsonFieldType.STRING).description("총 학교 수"),
-                                fieldWithPath("result.totalPages").type(JsonFieldType.STRING).description("총 페이지 수"),
+                                fieldWithPath("result.page").type(JsonFieldType.NUMBER).description("현재 페이지 번호 (0부터 시작)"),
+                                fieldWithPath("result.size").type(JsonFieldType.NUMBER).description("페이지 당 조회 수"),
+                                fieldWithPath("result.totalElements").type(JsonFieldType.NUMBER).description("총 학교 수"),
+                                fieldWithPath("result.totalPages").type(JsonFieldType.NUMBER).description("총 페이지 수"),
                                 fieldWithPath("result.hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 존재 여부"),
                                 fieldWithPath("result.hasPrevious").type(JsonFieldType.BOOLEAN).description("이전 페이지 존재 여부")
📝 Committable suggestion

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

Suggested change
fieldWithPath("result.page").type(JsonFieldType.STRING).description("현재 페이지 번호 (0부터 시작)"),
fieldWithPath("result.size").type(JsonFieldType.STRING).description("페이지 당 조회 수"),
fieldWithPath("result.totalElements").type(JsonFieldType.STRING).description("총 학교 수"),
fieldWithPath("result.totalPages").type(JsonFieldType.STRING).description("총 페이지 수"),
fieldWithPath("result.page").type(JsonFieldType.NUMBER).description("현재 페이지 번호 (0부터 시작)"),
fieldWithPath("result.size").type(JsonFieldType.NUMBER).description("페이지 당 조회 수"),
fieldWithPath("result.totalElements").type(JsonFieldType.NUMBER).description("총 학교 수"),
fieldWithPath("result.totalPages").type(JsonFieldType.NUMBER).description("총 페이지 수"),
fieldWithPath("result.hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 존재 여부"),
fieldWithPath("result.hasPrevious").type(JsonFieldType.BOOLEAN).description("이전 페이지 존재 여부")
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
around lines 76 - 79, In SchoolQueryControllerTest update the API docs for
pagination: change the four field descriptors that currently use
JsonFieldType.STRING—specifically the fieldWithPath entries for "result.page",
"result.size", "result.totalElements", and "result.totalPages"—to use
JsonFieldType.NUMBER so the documented types match the numeric values returned
by the Spring Data Page object; keep the descriptions the same and run the test
to ensure snippets regenerate correctly.

Comment on lines +104 to +108
fieldWithPath("result.chapterId").type(JsonFieldType.STRING).description("지부 ID"),
fieldWithPath("result.chapterName").type(JsonFieldType.STRING).description("지부 이름"),
fieldWithPath("result.schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("result.schoolId").type(JsonFieldType.STRING).description("학교 ID"),
fieldWithPath("result.remark").type(JsonFieldType.STRING).description("비고"),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

상세 조회 응답의 ID 필드 타입이 잘못 문서화되었습니다.

chapterId, schoolIdLong 타입인데 JsonFieldType.STRING으로 문서화되고 있습니다. Line 93의 SchoolInfo 생성 시 3L, 1L과 같은 Long 값을 사용하므로 실제 응답도 숫자입니다.

💚 타입 수정안
                         responseFields(fieldWithPath("success").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"),
                                 fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"),
                                 fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
-                                fieldWithPath("result.chapterId").type(JsonFieldType.STRING).description("지부 ID"),
+                                fieldWithPath("result.chapterId").type(JsonFieldType.NUMBER).description("지부 ID"),
                                 fieldWithPath("result.chapterName").type(JsonFieldType.STRING).description("지부 이름"),
                                 fieldWithPath("result.schoolName").type(JsonFieldType.STRING).description("학교 이름"),
-                                fieldWithPath("result.schoolId").type(JsonFieldType.STRING).description("학교 ID"),
+                                fieldWithPath("result.schoolId").type(JsonFieldType.NUMBER).description("학교 ID"),
                                 fieldWithPath("result.remark").type(JsonFieldType.STRING).description("비고"),
                                 fieldWithPath("result.createdAt").type(JsonFieldType.STRING).description("생성일자"),
                                 fieldWithPath("result.updatedAt").type(JsonFieldType.STRING).description("수정일자"))));
📝 Committable suggestion

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

Suggested change
fieldWithPath("result.chapterId").type(JsonFieldType.STRING).description("지부 ID"),
fieldWithPath("result.chapterName").type(JsonFieldType.STRING).description("지부 이름"),
fieldWithPath("result.schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("result.schoolId").type(JsonFieldType.STRING).description("학교 ID"),
fieldWithPath("result.remark").type(JsonFieldType.STRING).description("비고"),
responseFields(fieldWithPath("success").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"),
fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"),
fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
fieldWithPath("result.chapterId").type(JsonFieldType.NUMBER).description("지부 ID"),
fieldWithPath("result.chapterName").type(JsonFieldType.STRING).description("지부 이름"),
fieldWithPath("result.schoolName").type(JsonFieldType.STRING).description("학교 이름"),
fieldWithPath("result.schoolId").type(JsonFieldType.NUMBER).description("학교 ID"),
fieldWithPath("result.remark").type(JsonFieldType.STRING).description("비고"),
fieldWithPath("result.createdAt").type(JsonFieldType.STRING).description("생성일자"),
fieldWithPath("result.updatedAt").type(JsonFieldType.STRING).description("수정일자"))));
🤖 Prompt for AI Agents
In
@src/test/java/com/umc/product/organization/adapter/in/web/SchoolQueryControllerTest.java
around lines 104 - 108, The response field types for numeric IDs are documented
incorrectly: update the documented types for result.chapterId and
result.schoolId in SchoolQueryControllerTest (the fieldWithPath entries) from
JsonFieldType.STRING to JsonFieldType.NUMBER to match the Long values used when
creating SchoolInfo (e.g., 3L, 1L) so the generated API docs reflect numeric
IDs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +16 to +19
@RestController
@RequestMapping("/api/v1/admin/gisu")
@RequiredArgsConstructor
public class GisuController {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

필수: OpenAPI/Swagger 문서화를 위한 @tag 애너테이션이 누락되었습니다.

코딩 가이드라인에 따라 모든 public API 컨트롤러는 @Tag 애너테이션을 포함해야 합니다.

Based on coding guidelines, all public API endpoints must include OpenAPI/Swagger documentation.

📝 @tag 애너테이션 추가
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 @RestController
 @RequestMapping("/api/v1/admin/gisu")
 @RequiredArgsConstructor
+@Tag(name = "기수 관리", description = "기수(Gisu) 생성, 수정, 삭제 및 현재 기수 설정 API")
 public class GisuController {
🤖 Prompt for AI Agents
In
@src/main/java/com/umc/product/organization/adapter/in/web/GisuController.java
around lines 16 - 19, The GisuController class is missing the OpenAPI @Tag
annotation required for public API controllers; add an appropriate @Tag(...)
annotation above the class declaration on GisuController (the class annotated
with @RestController, @RequestMapping("/api/v1/admin/gisu"),
@RequiredArgsConstructor) with a concise name and description matching your API
documentation conventions so Swagger/OpenAPI picks up this controller in the
generated docs.

Comment on lines +27 to +34
@WebMvcTest(controllers = {
SchoolController.class,
SchoolQueryController.class,
ChapterController.class,
ChapterQueryController.class,
GisuController.class,
GisuQueryController.class,
})
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

테스트 격리성 및 성능을 위해 베이스 클래스 분리를 고려해주세요.

하나의 베이스 클래스에 6개의 컨트롤러를 모두 포함하면 각 테스트 클래스가 불필요한 컨트롤러까지 로딩하게 됩니다. 이는 테스트 실행 속도를 저하시키고 테스트 간 결합도를 높입니다.

도메인별(School, Chapter, Gisu) 또는 컨트롤러별로 베이스 클래스를 분리하는 것을 권장합니다.

♻️ 도메인별 베이스 클래스 분리 예시
// SchoolDocumentationTest.java
@WebMvcTest(controllers = {
    SchoolController.class,
    SchoolQueryController.class
})
public class SchoolDocumentationTest extends BaseDocumentationTest {
    @MockitoBean
    protected ManageSchoolUseCase manageSchoolUseCase;
    
    @MockitoBean
    protected GetSchoolUseCase getSchoolUseCase;
}

// GisuDocumentationTest.java
@WebMvcTest(controllers = {
    GisuController.class,
    GisuQueryController.class
})
public class GisuDocumentationTest extends BaseDocumentationTest {
    @MockitoBean
    protected ManageGisuUseCase manageGisuUseCase;
    
    @MockitoBean
    protected GetGisuUseCase getGisuUseCase;
}

// BaseDocumentationTest.java (공통 설정만)
@Import(RestDocsConfig.class)
@AutoConfigureMockMvc(addFilters = false)
@AutoConfigureRestDocs
public abstract class BaseDocumentationTest {
    @Autowired
    protected MockMvc mockMvc;
    
    @Autowired
    protected RestDocumentationResultHandler restDocsHandler;
    
    @Autowired
    protected ObjectMapper objectMapper;
    
    @MockitoBean
    protected JwtTokenProvider jwtTokenProvider;
}
🤖 Prompt for AI Agents
In @src/test/java/com/umc/product/support/DocumentationTest.java around lines 27
- 34, The test class DocumentationTest currently annotates WebMvcTest with six
controllers (SchoolController, SchoolQueryController, ChapterController,
ChapterQueryController, GisuController, GisuQueryController), causing
unnecessary controller loading and slower, coupled tests; split the tests into
domain-specific test classes (e.g., SchoolDocumentationTest,
ChapterDocumentationTest, GisuDocumentationTest) each annotated with WebMvcTest
referencing only their domain's controllers, move shared configuration and
autowired beans (MockMvc, RestDocumentationResultHandler, ObjectMapper,
JwtTokenProvider) into an abstract BaseDocumentationTest, and keep
domain-specific @MockitoBean mocks (ManageSchoolUseCase, GetSchoolUseCase,
ManageChapterUseCase, GetChapterUseCase, ManageGisuUseCase, GetGisuUseCase) in
their respective domain test classes.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] StudyGroup과 StudyGroupMember aggregate 패턴 적용 [Feat] Organization Doamin 내부 interface 설계

4 participants