Skip to content

[FEAT] 작가가 program을 만드는 api 개발 (#281)#282

Merged
redblackblossom merged 19 commits intomainfrom
feat/#281/photographer_program
Feb 12, 2026
Merged

[FEAT] 작가가 program을 만드는 api 개발 (#281)#282
redblackblossom merged 19 commits intomainfrom
feat/#281/photographer_program

Conversation

@redblackblossom
Copy link
Contributor

@redblackblossom redblackblossom commented Jan 25, 2026

📌 관련 이슈

✨ PR 세부 내용

작가가 program을 만드는 api 개발했습니다.

Summary by CodeRabbit

  • 새로운 기능

    • 사진작가용 프로그램 생성 API 추가 및 서버측 입력 유효성 검사 지원(제목, 설명, 가격, 소요시간).
    • 도메인 모델(프로그램, 제목/설명/가격/소요시간 값객체), 소프트 삭제 및 JPA 감사(생성/수정일) 적용.
    • 사용자 ID 주입을 위한 컨트롤러 인자 해석기와 @userid 어노테이션 추가.
    • 시스템 시계 주입용 설정 빈 추가.
  • 테스트

    • 서비스·도메인·컨버터·컨트롤러·아키텍처 규칙을 포함한 광범위한 단·통합 테스트 추가.

@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

프로그램 도메인(엔티티·값객체·컨버터), 리포지토리·스펙, 애플리케이션 서비스와 REST 컨트롤러를 추가하고, UserId 파라미터 리졸버, JPA 감사·Clock 빈, 빌드 설정(IDEA·검증·메타모델·ArchUnit) 및 관련 단위/통합 테스트들을 도입했습니다.

Changes

Cohort / File(s) Summary
Build config
reservation/build.gradle
IDEA 플러그인 추가, JPA 메타모델 어노테이션 프로세서, Spring Validation, ArchUnit 의존성 및 IDEA generated-sources 등록
Domain: Program & VOs
reservation/src/main/java/.../program/domain/Program.java, .../vo/Title.java, .../vo/Description.java, .../vo/Price.java, .../vo/Duration.java
Program JPA 엔티티(팩토리/업데이트/소프트삭제/소유권 검사)와 제목/설명/가격/소요시간 값 객체(검증·예외·toString/equals/hashCode) 추가
JPA Converters
reservation/src/main/java/.../infrastructure/converter/TitleConverter.java, DescriptionConverter.java, PriceConverter.java, DurationConverter.java
값객체 ↔ DB 타입(String/Long/Integer) 양방향 매핑을 위한 AttributeConverter 구현 및 autoApply 설정
Repository & Specifications
reservation/src/main/java/.../infrastructure/repository/ProgramRepository.java, ProgramSpecification.java
JpaRepository + JpaSpecificationExecutor 인터페이스 추가 및 isActive/isDeleted/belongsTo 스펙 제공
Application layer DTO & Service
reservation/src/main/java/.../program/application/ProgramService.java, .../dto/request/ProgramCreateRequest.java, .../dto/response/ProgramResponse.java
Program 생성용 요청/응답 DTO(검증 어노테이션 포함) 및 트랜잭셔널 서비스(createProgram) 추가
Presentation: Controller
reservation/src/main/java/.../program/presentation/ProgramController.java
POST /reservation/program 엔드포인트 추가(인증 어노테이션 사용, @UserId 주입, 요청 유효성 검사, ResultResponse 반환)
UserId resolver & MVC config
reservation/src/main/java/.../shared/presentation/web/resolver/UserId.java, UserIdArgumentResolver.java, config/WebMvcConfig.java
@userid 애노테이션, Passport 기반 HandlerMethodArgumentResolver 구현 및 WebMvcConfig에 등록
Infrastructure config
reservation/src/main/java/.../shared/infrastructure/ClockConfig.java, JpaAuditingConfig.java
Clock 빈 제공 및 @EnableJpaAuditing 설정 추가
Tests: Domain / Converters / Repository / Service / Controller / Resolver / ArchUnit
reservation/src/test/java/.../program/domain/**, .../infrastructure/converter/**, .../infrastructure/repository/ProgramRepositoryTest.java, .../application/ProgramServiceTest.java, .../presentation/ProgramControllerTest.java, .../shared/.../UserIdArgumentResolverTest.java, .../architecture/ControllerAuthenticationArchitectureTest.java
값객체·엔티티 단위테스트, 컨버터·레포지토리·서비스·컨트롤러·리졸버 통합/단위 테스트 및 ArchUnit 규칙 추가
Test fixtures
reservation/src/test/java/.../program/fixture/ProgramFixture.java
테스트용 Program 생성 유틸리티(기본값·삭제된 인스턴스 등) 추가

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as ProgramController
    participant Resolver as UserIdArgumentResolver
    participant Service as ProgramService
    participant Repository as ProgramRepository
    participant Database as Database

    Client->>Controller: POST /reservation/program\n(Authorization header, body)
    Controller->>Resolver: resolve `@UserId` (X-Passport header)
    Resolver->>Controller: Long photographerId
    Controller->>Controller: `@Valid` 요청 검증
    Controller->>Service: createProgram(photographerId, request)
    Service->>Service: Program.create(...) (VO 검증)
    Service->>Repository: save(program)
    Repository->>Database: INSERT program (converters 적용)
    Database-->>Repository: persisted entity (id)
    Repository-->>Service: saved Program
    Service-->>Controller: ProgramResponse(id)
    Controller-->>Client: 201 Created (ResultResponse<ProgramResponse>)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.83% 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 제목은 작가가 프로그램을 생성하는 API 개발이라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명에는 필수 섹션(관련 이슈, PR 세부 내용)이 포함되어 있지만, 세부 내용이 매우 간단하고 구체적인 구현 설명이 부족합니다.
Linked Issues check ✅ Passed PR의 모든 코드 변경사항이 이슈 #281의 '작가가 프로그램을 생성하는 API' 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 프로그램 생성 API 기능 개발과 관련된 범위 내에 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#281/photographer_program

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Jan 25, 2026

Test Results (reservation)

239 tests   239 ✅  7s ⏱️
 38 suites    0 💤
 38 files      0 ❌

Results for commit 3ca13d8.

♻️ This comment has been updated with latest results.

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: 9

🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java`:
- Around line 140-142: The delete() method sets deletedAt using
LocalDateTime.now(), which makes tests time-dependent; modify Program.delete to
accept a Clock parameter (e.g., delete(Clock clock)) and set deletedAt =
LocalDateTime.now(clock), and add a no-arg overload delete() that delegates to
delete(Clock.systemDefaultZone()) so existing callers remain unchanged; update
references to Program.delete in tests to pass a fixed Clock to allow
deterministic unit tests.
- Around line 164-179: The equals/hashCode use only the id field which breaks
collections when id is null; update Program.equals(Object) to treat transient
entities specially by returning this == o when either id is null (i.e., if id ==
null then fall back to instance equality) and otherwise compare ids, and update
Program.hashCode() to return System.identityHashCode(this) (or another stable
identity-based value) when id is null and Objects.hash(id) when id is non-null
so the hashCode remains stable before/after persistence.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Title.java`:
- Around line 26-35: The validate method in Title contains redundant length
check: after failing value.isBlank(), value.length() < MIN_LENGTH is always
false because MIN_LENGTH == 1; remove the redundant condition and simplify the
range check in Title.validate to only test value.length() > MAX_LENGTH (or test
value.length() < MIN_LENGTH when MIN_LENGTH > 1), keeping the null/isBlank check
and throwing DomainException with the same message; update any error message
logic to compute current length via value.length() and keep references to
validate, MIN_LENGTH and MAX_LENGTH intact.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java`:
- Around line 14-22: The findById(Long id) declaration in ProgramRepository
duplicates JpaRepository's method; remove this redundant method declaration from
ProgramRepository and keep the Javadoc elsewhere if you want to document
behavior. If you need a special query that ignores deletion flags, add a clearly
named repository method (e.g., findByIdIgnoringDeleted or use
ProgramSpecification with a method like findOne(Specification<Program> spec))
and implement/compose it instead of shadowing JpaRepository::findById.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecification.java`:
- Around line 21-23: Replace hard-coded field name strings in
ProgramSpecification methods (e.g., isActive(), and the methods using
"photographerId") with type-safe references: use the generated JPA metamodel
(Program_.deletedAt, Program_.photographerId) or introduce constants on the
Program entity (e.g., Program.Fields.DELETED_AT /
Program.Fields.PHOTOGRAPHER_ID) and use root.get(Program_.deletedAt) or
root.get(Program.Fields.DELETED_AT) instead of root.get("deletedAt"); ensure the
metamodel is generated (annotation processing enabled) or the new constants are
defined and imported before updating the other methods that currently use string
literals.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/application/ProgramServiceTest.java`:
- Around line 122-168: Update the tests in ProgramServiceTest that currently
only assert the exception type (the three tests calling
programService.createProgram) to also assert the exception message or error code
for stronger validation; catch or use assertion helpers (e.g., AssertJ's
assertThatThrownBy) to verify DomainException.getMessage() or a specific
errorCode property on the thrown DomainException matches the expected text/code
for empty title, negative price, and zero duration respectively so each test
asserts both type and precise failure reason.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java`:
- Around line 127-137: The test 동일한_ID를_가진_프로그램은_같다() currently compares
program1 to itself and doesn't verify ID-based equality; update the test to
create two distinct Program instances via createDefaultProgram(), assign the
same ID to both (use the Program class's setter or reflection to set the private
id field if no setter exists), then assert that program1.equals(program2)
(assertThat(program1).isEqualTo(program2)); reference the ProgramTest method
동일한_ID를_가진_프로그램은_같다() and helper createDefaultProgram() when locating where to
change the test and use Program#id (or private id field) as the unique
identifier to set.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecificationTest.java`:
- Around line 17-62: These tests only assert that
ProgramSpecification.isActive(), isDeleted(), and belongsTo(...) return non-null
Specifications; update them to verify actual predicate behavior by converting to
`@DataJpaTest` integration tests: persist sample Program entities (active/deleted
and with different owner IDs) via the repository, then call
repository.findAll(ProgramSpecification.isActive()),
findAll(ProgramSpecification.isDeleted()),
findAll(ProgramSpecification.belongsTo(1L)), and combinations like
isActive().and(belongsTo(1L)) and assert the returned lists contain the expected
entities; this exercises the real Criteria/Predicate logic rather than only null
checks.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolverTest.java`:
- Around line 72-137: Add a new test in the resolveArgument_테스트 nested class to
cover an expired Passport: create a Passport with an expired expiredAt (e.g.,
Instant.now().minus(...) so expiredAt < now), sign it using
passportHandler.sign(...) and mock NativeWebRequest/HttpServletRequest to return
that signed token for PassportHandler.PASSPORT_KEY, then call
resolver.resolveArgument(...) and assert it throws PresentationException (and
optionally assert the appropriate PresentationErrorCode for expired passport).
Ensure you reference the existing symbols Passport, passportHandler.sign,
PassportHandler.PASSPORT_KEY, resolver.resolveArgument, and
PresentationException when implementing the test.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

작가(Photographer)가 자신의 촬영 프로그램을 생성할 수 있는 API를 추가하고, Passport 기반 인증 사용자 ID 주입(@userid) 및 관련 테스트/아키텍처 검증을 도입합니다.

Changes:

  • Program 생성 유스케이스(도메인/서비스/리포지토리/컨트롤러) 및 요청/응답 DTO 추가
  • Passport 헤더에서 userId를 추출해 컨트롤러 파라미터로 주입하는 @UserId + ArgumentResolver 추가 및 MVC 설정에 등록
  • Program/VO/Converter/Controller/Service 관련 테스트 및 컨트롤러 인증 아키텍처(ArchUnit) 테스트 추가

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
reservation/build.gradle Validation starter 및 ArchUnit 테스트 의존성 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/presentation/ProgramController.java 프로그램 생성 REST API 엔드포인트 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/application/ProgramService.java 프로그램 생성 유스케이스 구현
reservation/src/main/java/net/catsnap/CatsnapReservation/program/application/dto/request/ProgramCreateRequest.java 프로그램 생성 요청 DTO(+ validation) 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/application/dto/response/ProgramResponse.java 프로그램 생성 응답 DTO 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java Program 애그리거트 및 소프트 삭제/수정 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Title.java 제목 VO 및 제약 검증 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Price.java 가격 VO 및 제약 검증/무료 여부 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Duration.java 소요시간 VO 및 표현/계산 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Description.java 설명 VO 및 제약/empty 판정 로직 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java Program JPA Repository 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecification.java Program 조회용 Specification 유틸 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/TitleConverter.java Title VO JPA 컨버터 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/PriceConverter.java Price VO JPA 컨버터 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DurationConverter.java Duration VO JPA 컨버터 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DescriptionConverter.java Description VO JPA 컨버터 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserId.java 컨트롤러 파라미터 userId 주입용 어노테이션 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolver.java Passport 헤더에서 userId 추출하는 ArgumentResolver 추가
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/config/WebMvcConfig.java ArgumentResolver 등록 로직 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/presentation/ProgramControllerTest.java ProgramController 생성 API/검증/인가 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/application/ProgramServiceTest.java ProgramService 생성 유스케이스 통합 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java Program 도메인 동작 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/TitleTest.java Title VO 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/PriceTest.java Price VO 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/DurationTest.java Duration VO 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/DescriptionTest.java Description VO 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/TitleConverterTest.java TitleConverter 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/PriceConverterTest.java PriceConverter 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DurationConverterTest.java DurationConverter 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DescriptionConverterTest.java DescriptionConverter 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecificationTest.java ProgramSpecification 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolverTest.java UserIdArgumentResolver 단위 테스트 추가
reservation/src/test/java/net/catsnap/CatsnapReservation/architecture/ControllerAuthenticationArchitectureTest.java 모든 컨트롤러 메서드 인증 어노테이션 강제 ArchUnit 테스트 추가

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

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: 6

🤖 Fix all issues with AI agents
In `@reservation/build.gradle`:
- Around line 76-77: Replace the hardcoded dependency coordinate
com.tngtech.archunit:archunit-junit5:1.4.1 with a single managed version: add an
entry like archunitVersion=1.4.1 to gradle.properties (or create a
libs.versions.toml alias such as libs.archunit.junit5) and update the dependency
declaration in both build files to use the property (e.g.,
"com.tngtech.archunit:archunit-junit5:${archunitVersion}") or the
version-catalog alias (libs.archunit.junit5) so the version is maintained in one
place.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java`:
- Around line 125-135: The update() method on Program must guard against
updating soft-deleted programs: at the start of Program.update(...) check the
program's deletion indicator (e.g., a boolean flag like isDeleted(), or a
timestamp field like deletedAt != null) and if the program is deleted throw an
appropriate exception (IllegalStateException or your domain-specific exception)
instead of applying new Title/Description/Price/Duration; keep the validation
before creating/applying Title, Description, Price, and Duration to prevent
modifications to soft-deleted entities.
- Around line 143-148: Program.delete(LocalDateTime) currently overwrites an
existing deletedAt; add duplicate-delete protection by checking the instance
field before assignment: keep the null-check for the incoming deletedAt, then if
this.deletedAt != null either make the operation idempotent by returning
immediately or throw a clear exception (e.g. IllegalStateException("이미
삭제되었습니다.")) — update the delete method in class Program and related tests to
reflect the chosen behavior.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/infrastructure/ClockConfig.java`:
- Around line 27-28: The ClockConfig.clock() currently returns
Clock.systemDefaultZone(); change it to return Clock.systemUTC() so the
application uses a stable UTC clock instead of JVM default timezone; update the
method in ClockConfig (replace Clock.systemDefaultZone() with Clock.systemUTC())
and ensure any places that format/display times perform explicit timezone
conversion for user-facing output.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java`:
- Around line 154-156: The createDefaultProgram() method in ProgramTest
duplicates fixture logic; replace its usage with ProgramFixture.createDefault()
to avoid duplicated defaults — update ProgramTest to call
ProgramFixture.createDefault() wherever createDefaultProgram() is used and
remove the private createDefaultProgram() helper to centralize default Program
creation in ProgramFixture.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepositoryTest.java`:
- Around line 16-21: Replace the inline auditing annotation in the
ProgramRepositoryTest class by removing `@EnableJpaAuditing` and importing the
centralized config: add `@Import`(JpaAuditingConfig.class) to the
ProgramRepositoryTest declaration so the test uses the shared JpaAuditingConfig
instead of enabling JPA auditing locally.

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: 2

🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java`:
- Around line 165-167: The isOwnedBy method on Program currently calls
this.photographerId.equals(photographerId); make it defensive against a null
parameter by changing the implementation in Program.isOwnedBy to safely compare
photographer IDs (for example, return Objects.equals(this.photographerId,
photographerId) or explicitly if (photographerId == null) return false; return
this.photographerId.equals(photographerId)); ensure you add the
java.util.Objects import if using Objects.equals and update the method body
accordingly.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java`:
- Around line 81-94: Add a new unit test that verifies Program.update(...)
throws when called on a deleted program: create a Program via
ProgramFixture.createDefaultDeleted(), then call program.update("새 제목", "새 설명",
200000L, 120) and assert that it throws IllegalStateException (use
assertThatThrownBy or equivalent). Name the test e.g. 삭제된_프로그램_수정_시_예외가_발생한다 and
place it alongside the existing 성공 테스트 to cover the deletion guard in
Program.update.

@redblackblossom redblackblossom merged commit 5ceeae2 into main Feb 12, 2026
10 checks passed
@redblackblossom redblackblossom deleted the feat/#281/photographer_program branch February 12, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨feature 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 작가가 자신의 프로그램 설정 기능 개발

2 participants