diff --git a/build.gradle b/build.gradle index df6062d..4d8ad9d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.5.13' id 'io.spring.dependency-management' version '1.1.7' + id 'org.asciidoctor.jvm.convert' version '4.0.0' } group = 'com.firstticket' @@ -46,6 +47,13 @@ repositories { password = githubToken } } + maven { + url = 'https://maven.pkg.github.com/first-ticket/common-jpa' + credentials { + username = githubUser + password = githubToken + } + } maven { url = 'https://maven.pkg.github.com/first-ticket/common-messaging' credentials { @@ -55,8 +63,13 @@ repositories { } } +configurations { + asciidoctorExt +} + ext { set('springCloudVersion', "2025.0.2") + snippetsDir = file("build/generated-snippets") } dependencyManagement { @@ -68,8 +81,8 @@ dependencyManagement { dependencies { // first-ticket 공통 모듈 implementation 'com.first-ticket:common:0.0.4-SNAPSHOT' - implementation 'com.first-ticket:common-jpa:0.0.1-SNAPSHOT' - implementation 'com.first-ticket:common-messaging:0.0.1-SNAPSHOT' + implementation 'com.first-ticket:common-jpa:0.0.2-SNAPSHOT' + implementation 'com.first-ticket:common-messaging:0.0.2-SNAPSHOT' // ── Spring Boot Core & Common ── // implementation 'org.springframework.boot:spring-boot-starter-web' @@ -122,6 +135,7 @@ dependencies { testImplementation 'com.h2database:h2' // Rest Docs + asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' } @@ -141,5 +155,36 @@ clean { } test { + outputs.dir snippetsDir // 스니펫을 snippetsDir에 저장 useJUnitPlatform() } + +asciidoctor { + inputs.dir snippetsDir + dependsOn test + configurations 'asciidoctorExt' + baseDirFollowsSourceFile() +} + +tasks.named('asciidoctor') { + doFirst { + delete file('src/main/resources/static/docs') + } +} + +tasks.register('copyDocument', Copy) { + dependsOn asciidoctor + from layout.buildDirectory.dir("docs/asciidoc") + into layout.projectDirectory.dir("src/main/resources/static/docs") +} + +tasks.named('build') { + dependsOn copyDocument +} + +bootJar { + dependsOn asciidoctor + from(layout.buildDirectory.dir("docs/asciidoc")) { + into 'static/docs' + } +} diff --git a/src/main/java/com/firstticket/programservice/domain/Program.java b/src/main/java/com/firstticket/programservice/domain/Program.java index 38c9cff..61277dc 100644 --- a/src/main/java/com/firstticket/programservice/domain/Program.java +++ b/src/main/java/com/firstticket/programservice/domain/Program.java @@ -263,10 +263,6 @@ private static void validateProgramInfo(String title, String category, String th if (type == null) { throw new ProgramException(ProgramErrorCode.INVALID_PROGRAM_TYPE); } - // region은 생성 시점에 확정되며 이후 변경하지 않는다 - if (region == null || region.isBlank()) { - throw new ProgramException(ProgramErrorCode.INVALID_REGION); - } if (region.length() > 100) { throw new ProgramException(ProgramErrorCode.INVALID_REGION); @@ -275,6 +271,7 @@ private static void validateProgramInfo(String title, String category, String th } private static String normalizeRegion(String region) { + // region은 생성 시점에 확정되며 이후 변경하지 않는다 if (region == null || region.isBlank()) { throw new ProgramException(ProgramErrorCode.INVALID_REGION); } diff --git a/src/main/java/com/firstticket/programservice/domain/Schedule.java b/src/main/java/com/firstticket/programservice/domain/Schedule.java index b09b137..c4288de 100644 --- a/src/main/java/com/firstticket/programservice/domain/Schedule.java +++ b/src/main/java/com/firstticket/programservice/domain/Schedule.java @@ -160,10 +160,10 @@ public void update(LocalDateTime eventStartAt, LocalDateTime eventEndAt, LocalDa LocalDateTime newSaleEnd = (saleEndAt != null) ? saleEndAt : this.saleEndAt; // 새로 입력된 판매 기간이 현재 시각보다 이전이면 차단 - if (newSaleStart.isBefore(currentTime)) { + if (saleStartAt != null && newSaleStart.isBefore(currentTime)) { throw new ProgramException(ProgramErrorCode.INVALID_SALE_PERIOD); } - if (newSaleEnd.isBefore(currentTime)) { + if (saleEndAt != null && newSaleEnd.isBefore(currentTime)) { throw new ProgramException(ProgramErrorCode.INVALID_SALE_PERIOD); } @@ -182,7 +182,7 @@ public void update(LocalDateTime eventStartAt, LocalDateTime eventEndAt, LocalDa // 이미 등록된 스케줄의 eventStartAt이 현재 시각보다 이전일 수 있으므로 // 변경하지 않는다면 과거 시점 판정을 하지 않음 - if (eventStartAt != null && newEventStart.isBefore(LocalDateTime.now())) { + if (eventStartAt != null && newEventStart.isBefore(currentTime)) { throw new ProgramException(ProgramErrorCode.PAST_EVENT_START); } diff --git a/src/main/java/com/firstticket/programservice/domain/service/dto/ScheduleRemainingData.java b/src/main/java/com/firstticket/programservice/domain/service/dto/ScheduleRemainingData.java index cca3b1c..2ca6852 100644 --- a/src/main/java/com/firstticket/programservice/domain/service/dto/ScheduleRemainingData.java +++ b/src/main/java/com/firstticket/programservice/domain/service/dto/ScheduleRemainingData.java @@ -2,6 +2,9 @@ import java.util.UUID; +import com.firstticket.programservice.domain.exception.ProgramErrorCode; +import com.firstticket.programservice.domain.exception.ProgramException; + /** * 좌석 서비스에서 조회한 회차별 잔여 좌석 수 데이터. * SeatProvider를 통해 조회된 결과를 담는다. @@ -13,7 +16,7 @@ public record ScheduleRemainingData( ) { public ScheduleRemainingData { if (scheduleId == null) { - throw new NullPointerException("scheduleId는 null일 수 없습니다."); + throw new ProgramException(ProgramErrorCode.INVALID_SCHEDULE_ID); } if (remainingCount < 0) { throw new IllegalArgumentException("remainingCount는 0 이상이어야 합니다."); diff --git a/src/main/java/com/firstticket/programservice/infrastructure/config/JpaConfig.java b/src/main/java/com/firstticket/programservice/infrastructure/config/JpaConfig.java new file mode 100644 index 0000000..ae76d26 --- /dev/null +++ b/src/main/java/com/firstticket/programservice/infrastructure/config/JpaConfig.java @@ -0,0 +1,16 @@ +package com.firstticket.programservice.infrastructure.config; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EntityScan(basePackages = { + "com.firstticket.programservice", + "com.firstticket.common.messaging" +}) +@EnableJpaRepositories(basePackages = { + "com.firstticket.programservice", + "com.firstticket.common.messaging" +}) +public class JpaConfig {} diff --git a/src/main/java/com/firstticket/programservice/infrastructure/event/ProgramEventPublisherImpl.java b/src/main/java/com/firstticket/programservice/infrastructure/event/ProgramEventPublisherImpl.java index 3b5f88e..33ae1f4 100644 --- a/src/main/java/com/firstticket/programservice/infrastructure/event/ProgramEventPublisherImpl.java +++ b/src/main/java/com/firstticket/programservice/infrastructure/event/ProgramEventPublisherImpl.java @@ -78,7 +78,7 @@ public void publishScheduleCreated(Program program, Schedule schedule, .toList(); Events.publish( - UUID.randomUUID().toString(), + schedule.getId() + "-" + scheduleCreated, "SCHEDULE", schedule.getId(), scheduleCreated, diff --git a/src/main/resources/db/migration/V1__init_program.sql b/src/main/resources/db/migration/V1__init_program.sql index 8bdc1a5..e9543ca 100644 --- a/src/main/resources/db/migration/V1__init_program.sql +++ b/src/main/resources/db/migration/V1__init_program.sql @@ -1,7 +1,7 @@ -- ===================================================== -- Program Service — V1 초기 스키마 --- schema: program --- Spring 설정: spring.jpa.properties.hibernate.default_schema=program +-- schema: program_schema +-- Spring 설정: spring.jpa.properties.hibernate.default_schema=program_schema -- ===================================================== -- ── btree_gist 확장 ──────────────────────────────────