diff --git a/.env b/.env new file mode 100644 index 000000000..aed2933ff --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +MYSQL_ROOT_PASSWORD=root +MYSQL_DATABASE=cinema +MYSQL_USER=cinema +MYSQL_PASSWORD=cinema diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..8af972cde --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..a2f2b9bec --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + + +### docker compose ### +cinema_mysql_data/ diff --git a/README.md b/README.md index 5fcc66b4d..1e3309b70 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,88 @@ -## [본 과정] 이커머스 핵심 프로세스 구현 -[단기 스킬업 Redis 교육 과정](https://hh-skillup.oopy.io/) 을 통해 상품 조회 및 주문 과정을 구현하며 현업에서 발생하는 문제를 Redis의 핵심 기술을 통해 해결합니다. -> Indexing, Caching을 통한 성능 개선 / 단계별 락 구현을 통한 동시성 이슈 해결 (낙관적/비관적 락, 분산락 등) +# hanghae-cinema +항해99 레디스 + +> spring boot 3.4.1 +> java 21 +> Mysql + +### 기록 +> **스프링부트 실행시 `AdapterApplication.java` 파일로 실행** + +> `application.properties`파일은 `adapter`모듈에서 설정(DB포함 전부) + * 스프링부트를 실행시키는 `SpringApplication`가 위치한곳 + +> `domain model`에 `setter`사용할 예정으로 빌더패턴 사용 안함. + +### 도커컴포즈 +* 도커컴포즈 설정파일 + * `docker-compose.yml` + * 탭: ❌, 스페이스바 2번: ⭕ + +* 도커컴포즈 변수 파일 + * `.env` + * 보안을 위해 `.gitignore`에 추가해야 하지만 교육과제므로 github에 올림 + * `.env.dev`, `.env.prod` 나누어 환경별로 다르게 적용도 가능 + +* 도커컴포즈 명령어 + * 실행 : $docker-compose up -d + * 종료 : $docker-compose down + * 실행 상태 확인 : $docker-compose ps + * 볼륨 재거 : $docker-compose down --volumes + * 로그 확인 : $docker-compose logs -f // `-f`옵션을 주면 실시간 + * 특정 서비스 시작 : $docker-compose start <서비스 이름> + * 특정 서비스 종료 : $docker-compose stop <서비스 이름> + + +-------------------------------------------------------------- +### 적용 아키텍처 +> 헥사고날 아키텍처 + +### 모듈 구성 +> cinema-adapter +> cinema-application +> cinema-domain +> cinema-infrastructure + +모듈은 헥사고날 아키텍처의 계층에 따라 나누었습니다. + +>1. cinema-adapter +> * 외부로부터의 요청을 받는 역할을 합니다. +> * 요청을 받아 `cinema-application`에 입력 포트를 호출 합니다. +> +>2. cinema-application +> * 입력/출력 포트에 대한 인터페이스를 정의하고 입력 포트에 비즈니스 로직을 구현 합니다. +> * 입력 포트는 Adapter에서 호출됩니다. +> * 출력 포트는 인터페이스만 정의하여 외부 시스템(DB등)과의 의존성을 최소화 합니다. +> * domain service 로직은 application 모듈 port(in)에서 호출합니다. +> +>3. cinema-domain +> * 외부에 의존하지 않는 독립적이고 핵심적인 비즈니스 모델 및 서비스 로직이 위치합니다. +> * 외부 기술에도 의존하지 않기에 순수 자바로만 작성합니다. +> * spring프레임워크등 프레임워크에도 의존하지 않습니다. +> * 그렇기 떄문에 트랜잭션 관리는 애플리케이션 계층에서 처리합니다. +> +>4. cinema-infrastructure +> * JPA 엔티티와 데이터베이스에 접근하는 계층입니다. +> * DB외에도 API등 외부 시스템과 상호작용 합니다. + +도메인과 jpa엔티티를 나누었고 mapper클래스로 변환하도록 하였습니다. + +### 테이블 디자인 + +아래와 같이 7개 테이블로 구성하였습니다. +> `영화` +> `상영관` +> `상영시간표` +> `영화예매내역` +> `상영관좌석` +> `회원` +> `업로드파일` + +* ERD + * `docs/cinema_erd.png` +* 테이블 생성 쿼리 + * `docs/cineam_create.sql` +* 초기 데이터 + * `docs/init_insert.sql` + +`영화`테이블의 `영상물등급`, `영화장르` 컬럼에 적재되는 하는 코드값은 `java`에 `enum`을 사용해 처리 diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..70a783e4b --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + java + id("org.springframework.boot") version "3.4.1" + id("io.spring.dependency-management") version "1.1.7" +} + +group = "com.hanghae" +version = "0.0.1-SNAPSHOT" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +configurations { + compileOnly { + extendsFrom(configurations.annotationProcessor.get()) + } +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly("org.projectlombok:lombok") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + developmentOnly("org.springframework.boot:spring-boot-devtools") +} + +subprojects { + apply(plugin = "java") + apply(plugin = "io.spring.dependency-management") + + dependencies { + implementation("jakarta.transaction:jakarta.transaction-api") + implementation("org.springframework.boot:spring-boot-starter") + testImplementation("org.springframework.boot:spring-boot-starter-test") + annotationProcessor("org.projectlombok:lombok") + compileOnly("org.projectlombok:lombok") + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + } +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/cinema-adapter/build.gradle.kts b/cinema-adapter/build.gradle.kts new file mode 100644 index 000000000..63fb3df72 --- /dev/null +++ b/cinema-adapter/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("java") + id("org.springframework.boot") version "3.4.1" + id("io.spring.dependency-management") version "1.1.7" +} + +group = "com.hanghae" +version = "0.0.1-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":cinema-application")) // 애플리케이션 계층 의존성 + implementation(project(":cinema-infrastructure")) // RepositoryPort 구현체를 찾지 못해서 추가 + implementation("org.springframework.boot:spring-boot-starter-web") // web + implementation("org.springframework.boot:spring-boot-starter-data-jpa") //jpa 레포지토리 의존성 문제로 추가 (@EnableJpaRepositories) +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/cinema-adapter/src/main/java/com/hanghae/adapter/AdapterApplication.java b/cinema-adapter/src/main/java/com/hanghae/adapter/AdapterApplication.java new file mode 100644 index 000000000..70e71c299 --- /dev/null +++ b/cinema-adapter/src/main/java/com/hanghae/adapter/AdapterApplication.java @@ -0,0 +1,30 @@ +package com.hanghae.adapter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +/* +1. 다른 모듈의 빈을 찾지 못하는 문제 해결을 위해 "@ComponentScan" 어노테이션 추가 +2. JpaRepository를 찾지 못해 "@EnableJpaRepositories" 추가 +3. Jpa Entity를 찾지 못해 "@EntityScan" 추가 +4. application.properties 파일은 스프링부트를 실행시키는 "cinema-adapter" 모듈에 작성해야 적용됨 + * 타모듈 application.properties에 설정해도 적용 안됨 + */ +@SpringBootApplication +@EnableJpaRepositories(basePackages = "com.hanghae.infrastructure.repository") +@EntityScan(basePackages = "com.hanghae.infrastructure.entity") +@ComponentScan(basePackages = { + "com.hanghae.domain", + "com.hanghae.application", + "com.hanghae.infrastructure", + "com.hanghae.adapter" +}) +public class AdapterApplication { + public static void main(String[] args) { + //서버 실행시 어뎁터로 실행 + SpringApplication.run(AdapterApplication.class, args); + } +} diff --git a/cinema-adapter/src/main/java/com/hanghae/adapter/web/MovieController.java b/cinema-adapter/src/main/java/com/hanghae/adapter/web/MovieController.java new file mode 100644 index 000000000..4f9ac7b08 --- /dev/null +++ b/cinema-adapter/src/main/java/com/hanghae/adapter/web/MovieController.java @@ -0,0 +1,20 @@ +package com.hanghae.adapter.web; + +import com.hanghae.application.dto.MovieScheduleResponseDto; +import com.hanghae.application.port.in.MovieScheduleService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class MovieController { + private final MovieScheduleService movieScheduleService; + + @GetMapping("/api/v1/movie-schedules") + public List getMovieSchedules() { + return movieScheduleService.getMovieSchedules(); + } +} diff --git a/cinema-adapter/src/main/java/com/hanghae/adapter/web/exception/ErrorResponse.java b/cinema-adapter/src/main/java/com/hanghae/adapter/web/exception/ErrorResponse.java new file mode 100644 index 000000000..0f660a6de --- /dev/null +++ b/cinema-adapter/src/main/java/com/hanghae/adapter/web/exception/ErrorResponse.java @@ -0,0 +1,5 @@ +package com.hanghae.adapter.web.exception; + + +public record ErrorResponse(int status, String message) { +} diff --git a/cinema-adapter/src/main/java/com/hanghae/adapter/web/exception/GlobalExceptionHandler.java b/cinema-adapter/src/main/java/com/hanghae/adapter/web/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..4454cc9dd --- /dev/null +++ b/cinema-adapter/src/main/java/com/hanghae/adapter/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,15 @@ +package com.hanghae.adapter.web.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntimeException(RuntimeException e) { + ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + } +} diff --git a/cinema-adapter/src/main/resources/application.properties b/cinema-adapter/src/main/resources/application.properties new file mode 100644 index 000000000..2e86227cf --- /dev/null +++ b/cinema-adapter/src/main/resources/application.properties @@ -0,0 +1,30 @@ +spring.application.name=cinema + +# 서버 포트 설정 (기본값: 8080) +server.port=8080 + +# MySQL 설정 +spring.datasource.url=jdbc:mysql://localhost:3307/cinema?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&useUnicode=true +spring.datasource.username=cinema +spring.datasource.password=cinema +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# JPA 설정 +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect +spring.jpa.show-sql=true +spring.jpa.format-sql=true + +# 쿼리를 가독성 있게 포맷팅 +spring.jpa.properties.hibernate.format_sql=true + +# SQL 실행 로그 출력, 파라미터 값까지 출력 (Spring Boot 2.1 이상 지원) +spring.jpa.properties.hibernate.type=TRACE +spring.jpa.properties.hibernate.highlight_sql=true + +# 바인딩된 파라미터 값도 출력 (Hibernate 5.3 이상) +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql=TRACE + +# log +#logging.level.root=info diff --git a/cinema-application/build.gradle.kts b/cinema-application/build.gradle.kts new file mode 100644 index 000000000..f8d355bb7 --- /dev/null +++ b/cinema-application/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("java") + id("org.springframework.boot") version "3.4.1" + id("io.spring.dependency-management") version "1.1.7" +} + +group = "com.hanghae" +version = "0.0.1-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":cinema-domain")) // 도메인 계층 의존성 +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/cinema-application/src/main/java/com/hanghae/application/dto/MovieScheduleResponseDto.java b/cinema-application/src/main/java/com/hanghae/application/dto/MovieScheduleResponseDto.java new file mode 100644 index 000000000..ce2235608 --- /dev/null +++ b/cinema-application/src/main/java/com/hanghae/application/dto/MovieScheduleResponseDto.java @@ -0,0 +1,15 @@ +package com.hanghae.application.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public record MovieScheduleResponseDto( + String title, //영화 제목 + String rating, //영상물 등급 + LocalDate releaseDate, //개봉일 + String thumbnailPath, // 썸네일 경로 + Long runningTimeMinutes, // 러닝타임(분) + String genre, // 영화 장르 + String screenName, // 상영관 이름 + LocalDateTime showStartAt // 상영시작시간 +) {} diff --git a/cinema-application/src/main/java/com/hanghae/application/port/in/MovieScheduleService.java b/cinema-application/src/main/java/com/hanghae/application/port/in/MovieScheduleService.java new file mode 100644 index 000000000..55b5f28d1 --- /dev/null +++ b/cinema-application/src/main/java/com/hanghae/application/port/in/MovieScheduleService.java @@ -0,0 +1,9 @@ +package com.hanghae.application.port.in; + +import com.hanghae.application.dto.MovieScheduleResponseDto; + +import java.util.List; + +public interface MovieScheduleService { + List getMovieSchedules(); +} diff --git a/cinema-application/src/main/java/com/hanghae/application/port/out/ScreeningScheduleRepositoryPort.java b/cinema-application/src/main/java/com/hanghae/application/port/out/ScreeningScheduleRepositoryPort.java new file mode 100644 index 000000000..71801d1ba --- /dev/null +++ b/cinema-application/src/main/java/com/hanghae/application/port/out/ScreeningScheduleRepositoryPort.java @@ -0,0 +1,9 @@ +package com.hanghae.application.port.out; + +import com.hanghae.domain.model.ScreeningSchedule; + +import java.util.List; + +public interface ScreeningScheduleRepositoryPort { + List findAll(); +} diff --git a/cinema-application/src/main/java/com/hanghae/application/service/MovieScheduleServiceImpl.java b/cinema-application/src/main/java/com/hanghae/application/service/MovieScheduleServiceImpl.java new file mode 100644 index 000000000..0ec5baf13 --- /dev/null +++ b/cinema-application/src/main/java/com/hanghae/application/service/MovieScheduleServiceImpl.java @@ -0,0 +1,56 @@ +package com.hanghae.application.service; + +import com.hanghae.application.dto.MovieScheduleResponseDto; +import com.hanghae.application.port.in.MovieScheduleService; +import com.hanghae.application.port.out.ScreeningScheduleRepositoryPort; +import com.hanghae.domain.model.Movie; +import com.hanghae.domain.model.ScreeningSchedule; +import com.hanghae.domain.model.UploadFile; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MovieScheduleServiceImpl implements MovieScheduleService { + private final ScreeningScheduleRepositoryPort repositoryPort; + + @Override + @Transactional + public List getMovieSchedules() { + List schedules = repositoryPort.findAll(); + + return schedules.stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } + + private MovieScheduleResponseDto convertToDto(ScreeningSchedule schedule) { + Movie movie = schedule.getMovie(); + String thumbnailPath = getThumbnailPath(movie.getUploadFile()); + + return new MovieScheduleResponseDto( + movie.getTitle(), + movie.getRating().getCodeName(), + movie.getReleaseDate(), + thumbnailPath, + movie.getRunningTimeMinutes(), + movie.getGenre().getCodeName(), + schedule.getScreen().getScreenName(), + schedule.getShowStartAt() + ); + } + + private String getThumbnailPath(UploadFile uploadFile) { + if (uploadFile == null) { + return ""; + } + String filePath = Optional.ofNullable(uploadFile.getFilePath()).orElse(""); + String fileName = Optional.ofNullable(uploadFile.getFileName()).orElse(""); + return filePath + fileName; + } +} diff --git a/cinema-domain/build.gradle.kts b/cinema-domain/build.gradle.kts new file mode 100644 index 000000000..dd17cc9aa --- /dev/null +++ b/cinema-domain/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("java") + id("org.springframework.boot") version "3.4.1" + id("io.spring.dependency-management") version "1.1.7" +} + +group = "com.hanghae" +version = "0.0.1-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/cinema-domain/src/main/java/com/hanghae/domain/model/Movie.java b/cinema-domain/src/main/java/com/hanghae/domain/model/Movie.java new file mode 100644 index 000000000..4fca9a80e --- /dev/null +++ b/cinema-domain/src/main/java/com/hanghae/domain/model/Movie.java @@ -0,0 +1,27 @@ +package com.hanghae.domain.model; + +import com.hanghae.domain.model.enums.MovieGenre; +import com.hanghae.domain.model.enums.MovieRating; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +public class Movie { + //일관된 상태 유지를 위해 @AllArgsConstructor 만 사용 + private final Long movieId; + private final String title; // 영화 제목 + private final MovieRating rating; // 영상물 등급 + private final LocalDate releaseDate; // 개봉일 + private final Long runningTimeMinutes; // 러닝 타임(분) + private final MovieGenre genre; // 영화 장르 + private final UploadFile uploadFile; // 썸네일 ID + private final Long createdBy; // 작성자 ID + private final LocalDateTime createdAt; // 작성일 + private final Long updatedBy; // 수정자 ID + private final LocalDateTime updatedAt; //수정일 +} diff --git a/cinema-domain/src/main/java/com/hanghae/domain/model/Screen.java b/cinema-domain/src/main/java/com/hanghae/domain/model/Screen.java new file mode 100644 index 000000000..4ea0fa890 --- /dev/null +++ b/cinema-domain/src/main/java/com/hanghae/domain/model/Screen.java @@ -0,0 +1,17 @@ +package com.hanghae.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +public class Screen { + private final Long screenId; + private final String screenName; //상영관 이름 + private final Long createdBy; // 작성자 ID + private final LocalDateTime createdAt; // 작성일 + private final Long updatedBy; // 수정자 ID + private final LocalDateTime updatedAt; //수정일 +} diff --git a/cinema-domain/src/main/java/com/hanghae/domain/model/ScreeningSchedule.java b/cinema-domain/src/main/java/com/hanghae/domain/model/ScreeningSchedule.java new file mode 100644 index 000000000..c4a9ff626 --- /dev/null +++ b/cinema-domain/src/main/java/com/hanghae/domain/model/ScreeningSchedule.java @@ -0,0 +1,45 @@ +package com.hanghae.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +public class ScreeningSchedule { + //일관된 상태 유지를 위해 @AllArgsConstructor 만 사용 + private final Long scheduleId; + private final Movie movie; // 영화 + private final Screen screen; // 상영관 + private final LocalDateTime showStartAt; + private final Long createdBy; // 작성자 ID + private final LocalDateTime createdAt; // 작성일 + private final Long updatedBy; // 수정자 ID + private final LocalDateTime updatedAt; //수정일 + + public ScreeningSchedule(Long scheduleId, Movie movie, Screen screen, LocalDateTime showStartAt, Long createdBy, LocalDateTime createdAt, Long updatedBy, LocalDateTime updatedAt) { + this.scheduleId = scheduleId; + this.movie = movie; + this.screen = screen; + this.showStartAt = showStartAt; + this.createdBy = createdBy; + this.createdAt = createdAt; + this.updatedBy = updatedBy; + this.updatedAt = updatedAt; + + // 개봉일 검증 로직 + validateReleaseDate(movie.getReleaseDate(), showStartAt); + } + + // 패키지 범위 setter: 외부에서는 호출 불가 + void setShowStartAt(LocalDateTime showStartAt) { + validateReleaseDate(movie.getReleaseDate(), showStartAt); + } + + public void validateReleaseDate(LocalDate releaseDate, LocalDateTime showStartAt) { + if (showStartAt.toLocalDate().isBefore(releaseDate)) { + throw new IllegalArgumentException("상영 날짜는 개봉일보다 이전일 수 없습니다."); + } + } +} diff --git a/cinema-domain/src/main/java/com/hanghae/domain/model/UploadFile.java b/cinema-domain/src/main/java/com/hanghae/domain/model/UploadFile.java new file mode 100644 index 000000000..d693da073 --- /dev/null +++ b/cinema-domain/src/main/java/com/hanghae/domain/model/UploadFile.java @@ -0,0 +1,21 @@ +package com.hanghae.domain.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +public class UploadFile { + //일관된 상태 유지를 위해 @AllArgsConstructor 만 사용 + private final Long fileId; + private final String filePath; // 파일 경로 + private final String fileName; // 파일 이름 + private final String originFileName; // 원본 파일 이름 + private final Long createdBy; // 작성자 ID + private final LocalDateTime createdAt; // 작성일 + private final Long updatedBy; // 수정자 ID + private final LocalDateTime updatedAt; //수정일 +} diff --git a/cinema-domain/src/main/java/com/hanghae/domain/model/enums/MovieGenre.java b/cinema-domain/src/main/java/com/hanghae/domain/model/enums/MovieGenre.java new file mode 100644 index 000000000..0a38f327d --- /dev/null +++ b/cinema-domain/src/main/java/com/hanghae/domain/model/enums/MovieGenre.java @@ -0,0 +1,27 @@ +package com.hanghae.domain.model.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum MovieGenre { + FAMILY("가족", "F"), + ACTION("액션", "A"), + THRILLER("스릴러", "T"), + ROMANCE("로맨스", "R"), + COMEDY("코메디", "C"); + + private final String codeName; // 사용자에게 보여줄 값 + private final String dbCode; // DB에 저장될 코드값 + + //db코드를 enum코드로 변경 + public static MovieGenre fromDbCode(String dbCode) { + for (MovieGenre genre : values()) { + if (genre.dbCode.equals(dbCode)) { + return genre; + } + } + throw new IllegalArgumentException("일치하는 영화 장르 코드 값이 없습니다 : " + dbCode); + } +} diff --git a/cinema-domain/src/main/java/com/hanghae/domain/model/enums/MovieRating.java b/cinema-domain/src/main/java/com/hanghae/domain/model/enums/MovieRating.java new file mode 100644 index 000000000..6021a6b94 --- /dev/null +++ b/cinema-domain/src/main/java/com/hanghae/domain/model/enums/MovieRating.java @@ -0,0 +1,26 @@ +package com.hanghae.domain.model.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum MovieRating { + ALL("전체이용가", "1"), + TWELVE("12세", "2"), + FIFTEEN("15세", "3"), + ADULT("청소년 관람불가", "4"); + + private final String codeName; // 사용자에게 보여줄 값 + private final String dbCode; // DB에 저장될 코드값 + + //db코드를 enum코드로 변경 + public static MovieRating fromDbCode(String dbCode) { + for (MovieRating rating : values()) { + if (rating.dbCode.equals(dbCode)) { + return rating; + } + } + throw new IllegalArgumentException("일치하는 영상물 등급 코드 값이 없습니다 : " + dbCode); + } +} diff --git a/cinema-infrastructure/build.gradle.kts b/cinema-infrastructure/build.gradle.kts new file mode 100644 index 000000000..2c9b0297a --- /dev/null +++ b/cinema-infrastructure/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("java") + id("org.springframework.boot") version "3.4.1" + id("io.spring.dependency-management") version "1.1.7" +} + +group = "com.hanghae" +version = "0.0.1-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":cinema-domain")) //도메인 계층 의존성 + implementation(project(":cinema-application")) // 애플리케이션 계층 의존성 + implementation("org.springframework.boot:spring-boot-starter-data-jpa") //db + runtimeOnly("com.h2database:h2") //db + runtimeOnly("com.mysql:mysql-connector-j") //db +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/adapter/ScreeningScheduleRepositoryAdapter.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/adapter/ScreeningScheduleRepositoryAdapter.java new file mode 100644 index 000000000..32f7e86ba --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/adapter/ScreeningScheduleRepositoryAdapter.java @@ -0,0 +1,60 @@ +package com.hanghae.infrastructure.adapter; + +import com.hanghae.application.port.out.ScreeningScheduleRepositoryPort; +import com.hanghae.domain.model.Movie; +import com.hanghae.domain.model.Screen; +import com.hanghae.domain.model.ScreeningSchedule; +import com.hanghae.infrastructure.entity.ScreeningScheduleEntity; +import com.hanghae.infrastructure.mapper.UploadFileMapper; +import com.hanghae.infrastructure.repository.ScreeningScheduleRepositoryJpa; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class ScreeningScheduleRepositoryAdapter implements ScreeningScheduleRepositoryPort { + + private final ScreeningScheduleRepositoryJpa repository; + + @Override + public List findAll() { + return repository.findAll().stream() + .map(this::toDomain) + .collect(Collectors.toList()); + } + + private ScreeningSchedule toDomain(ScreeningScheduleEntity entity) { + return new ScreeningSchedule( + entity.getId(), + new Movie( + entity.getMovieEntity().getId(), + entity.getMovieEntity().getTitle(), + entity.getMovieEntity().getRating(), + entity.getMovieEntity().getReleaseDate(), + entity.getMovieEntity().getRunningTimeMinutes(), + entity.getMovieEntity().getGenre(), + UploadFileMapper.toDomain(entity.getMovieEntity().getUploadFileEntity()), + entity.getMovieEntity().getCreatedBy(), + entity.getMovieEntity().getCreatedAt(), + entity.getMovieEntity().getUpdatedBy(), + entity.getMovieEntity().getUpdatedAt() + ), + new Screen( + entity.getScreenEntity().getId(), + entity.getScreenEntity().getScreenName(), + entity.getScreenEntity().getCreatedBy(), + entity.getScreenEntity().getCreatedAt(), + entity.getScreenEntity().getUpdatedBy(), + entity.getScreenEntity().getUpdatedAt() + ), + entity.getShowStartAt(), + entity.getCreatedBy(), + entity.getCreatedAt(), + entity.getUpdatedBy(), + entity.getUpdatedAt() + ); + } +} \ No newline at end of file diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/converter/MovieGenreConverter.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/converter/MovieGenreConverter.java new file mode 100644 index 000000000..f97d303c7 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/converter/MovieGenreConverter.java @@ -0,0 +1,24 @@ +package com.hanghae.infrastructure.converter; + +import com.hanghae.domain.model.enums.MovieGenre; +import jakarta.persistence.AttributeConverter; + +public class MovieGenreConverter implements AttributeConverter { + /** + * MovieGenre enum 코드변환 메서드 + * DB저장시 코드 변환해 저장 + * DB조회시 다시 enum코드로 변환해서 반환 + */ + + @Override + public String convertToDatabaseColumn(MovieGenre attribute) { + //enum코드 db코드로 변환 + return attribute != null ? attribute.getDbCode() : null; + } + + @Override + public MovieGenre convertToEntityAttribute(String dbData) { + //db코드 enum코드로 변환 + return dbData != null ? MovieGenre.fromDbCode(dbData) : null; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/converter/MovieRatingConverter.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/converter/MovieRatingConverter.java new file mode 100644 index 000000000..730906d03 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/converter/MovieRatingConverter.java @@ -0,0 +1,24 @@ +package com.hanghae.infrastructure.converter; + +import com.hanghae.domain.model.enums.MovieRating; +import jakarta.persistence.AttributeConverter; + +public class MovieRatingConverter implements AttributeConverter { + /** + * MovieRating enum 코드변환 메서드 + * DB저장시 코드 변환해 저장 + * DB조회시 다시 enum코드로 변환해서 반환 + */ + + @Override + public String convertToDatabaseColumn(MovieRating attribute) { + //enum코드 db코드로 변환 + return attribute != null ? attribute.getDbCode() : null; + } + + @Override + public MovieRating convertToEntityAttribute(String dbData) { + //db코드 enum코드로 변환 + return dbData != null ? MovieRating.fromDbCode(dbData) : null; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/BaseEntity.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/BaseEntity.java new file mode 100644 index 000000000..c382e4dd7 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/BaseEntity.java @@ -0,0 +1,35 @@ +package com.hanghae.infrastructure.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +public class BaseEntity { + @Column(updatable = false) + private Long createdBy; // 작성자 ID + + @CreationTimestamp + @Column(updatable = false) + private LocalDateTime createdAt; // 작성일 + + @Column + private Long updatedBy; // 수정자 ID + + @UpdateTimestamp + @Column(insertable = false) + private LocalDateTime updatedAt; //수정일 + + protected void setCreatedBy(Long createdBy) { // protected + this.createdBy = createdBy; + } + + protected void setUpdatedBy(Long updatedBy) { //protected + this.updatedBy = updatedBy; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/MovieEntity.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/MovieEntity.java new file mode 100644 index 000000000..8b41a6698 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/MovieEntity.java @@ -0,0 +1,66 @@ +package com.hanghae.infrastructure.entity; + +import com.hanghae.domain.model.enums.MovieGenre; +import com.hanghae.domain.model.enums.MovieRating; +import com.hanghae.infrastructure.converter.MovieGenreConverter; +import com.hanghae.infrastructure.converter.MovieRatingConverter; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDate; +import java.time.LocalDateTime; + + +@Entity +@Getter +@DynamicInsert +@DynamicUpdate +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name= "movie") +public class MovieEntity extends BaseEntity { + @Id + @Column(name = "movie_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; // 영화 제목 + + @Convert(converter = MovieRatingConverter.class) //enum > db코드 변환 + @Column(nullable = false) + private MovieRating rating; // 영상물 등급 ID + + @Column + private LocalDate releaseDate; // 개봉일 + + @Column(nullable = false) + private Long runningTimeMinutes; // 러닝 타임(분) + + @Convert(converter = MovieGenreConverter.class) //enum > db코드 변환 + @Column(nullable = false) + private MovieGenre genre; // 영화 장르 ID + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "file_id") + private UploadFileEntity uploadFileEntity; // 업로드 파일 (썸네일) + + @Builder + public MovieEntity(Long id, String title, MovieRating rating, LocalDate releaseDate, Long runningTimeMinutes, MovieGenre genre, UploadFileEntity uploadFileEntity, Long createdBy, Long updatedBy) { + this.id = id; + this.title = title; + this.rating = rating; + this.releaseDate = releaseDate; + this.runningTimeMinutes = runningTimeMinutes; + this.genre = genre; + this.uploadFileEntity = uploadFileEntity; + this.setCreatedBy(createdBy); + this.setUpdatedBy(updatedBy); + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/ScreenEntity.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/ScreenEntity.java new file mode 100644 index 000000000..22b3df796 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/ScreenEntity.java @@ -0,0 +1,38 @@ +package com.hanghae.infrastructure.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@DynamicInsert +@DynamicUpdate +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name= "screen") +public class ScreenEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "screen_id") + private Long id; + + @Column + private String screenName; //상영관 이름 + + @Builder + public ScreenEntity(Long id, String screenName, Long createdBy, Long updatedBy) { + this.id = id; + this.screenName = screenName; + this.setCreatedBy(createdBy); + this.setUpdatedBy(updatedBy); + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/ScreeningScheduleEntity.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/ScreeningScheduleEntity.java new file mode 100644 index 000000000..be2acbfa1 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/ScreeningScheduleEntity.java @@ -0,0 +1,48 @@ +package com.hanghae.infrastructure.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@DynamicInsert +@DynamicUpdate +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name= "screening_schedule") +public class ScreeningScheduleEntity extends BaseEntity { + @Id + @Column(name = "schedule_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "movie_id") + private MovieEntity movieEntity; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "screen_id") + private ScreenEntity screenEntity; + + @Column + private LocalDateTime showStartAt; // 상영 시작 시간 + + @Builder + public ScreeningScheduleEntity(Long id, MovieEntity movieEntity, ScreenEntity screenEntity, LocalDateTime showStartAt, Long createdBy, Long updatedBy) { + this.id = id; + this.movieEntity = movieEntity; + this.screenEntity = screenEntity; + this.showStartAt = showStartAt; + this.setCreatedBy(createdBy); + this.setUpdatedBy(updatedBy); + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/UploadFileEntity.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/UploadFileEntity.java new file mode 100644 index 000000000..26ae5552b --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/entity/UploadFileEntity.java @@ -0,0 +1,46 @@ +package com.hanghae.infrastructure.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Getter +@DynamicInsert +@DynamicUpdate +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name= "upload_file") +public class UploadFileEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "file_id") + private Long id; + + @Column + private String filePath; // 파일 경로 + + @Column + private String fileName; // 파일 이름 + + @Column + private String originFileName; // 원본 파일 이름 + + @Builder + public UploadFileEntity(Long id, String filePath, String fileName, String originFileName, Long createdBy, Long updatedBy) { + this.id = id; + this.filePath = filePath; + this.fileName = fileName; + this.originFileName = originFileName; + this.setCreatedBy(createdBy); + this.setUpdatedBy(updatedBy); + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/MovieMapper.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/MovieMapper.java new file mode 100644 index 000000000..95dbc7dc7 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/MovieMapper.java @@ -0,0 +1,37 @@ +package com.hanghae.infrastructure.mapper; + +import com.hanghae.domain.model.Movie; +import com.hanghae.infrastructure.entity.MovieEntity; + +public class MovieMapper { + public static Movie toDomain(MovieEntity movieEntity) { + return new Movie( + movieEntity.getId(), + movieEntity.getTitle(), + movieEntity.getRating(), + movieEntity.getReleaseDate(), + movieEntity.getRunningTimeMinutes(), + movieEntity.getGenre(), + UploadFileMapper.toDomain(movieEntity.getUploadFileEntity()), + movieEntity.getCreatedBy(), + movieEntity.getCreatedAt(), + movieEntity.getUpdatedBy(), + movieEntity.getUpdatedAt() + ); + } + + public static MovieEntity toEntity(Movie movie) { + MovieEntity movieEntity = MovieEntity.builder() + .id(movie.getMovieId()) + .title(movie.getTitle()) + .rating(movie.getRating()) + .releaseDate(movie.getReleaseDate()) + .runningTimeMinutes(movie.getRunningTimeMinutes()) + .genre(movie.getGenre()) + .uploadFileEntity(UploadFileMapper.toEntity(movie.getUploadFile())) + .createdBy(movie.getCreatedBy()) + .updatedBy(movie.getUpdatedBy()) + .build(); + return movieEntity; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/ScreenMapper.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/ScreenMapper.java new file mode 100644 index 000000000..a249b3969 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/ScreenMapper.java @@ -0,0 +1,27 @@ +package com.hanghae.infrastructure.mapper; + +import com.hanghae.domain.model.Screen; +import com.hanghae.infrastructure.entity.ScreenEntity; + +public class ScreenMapper { + public static Screen toDomain(ScreenEntity entity) { + return new Screen( + entity.getId(), + entity.getScreenName(), + entity.getCreatedBy(), + entity.getCreatedAt(), + entity.getUpdatedBy(), + entity.getUpdatedAt() + ); + } + + public static ScreenEntity toEntity(Screen domain) { + ScreenEntity entity = ScreenEntity.builder() + .id(domain.getScreenId()) + .screenName(domain.getScreenName()) + .createdBy(domain.getCreatedBy()) + .updatedBy(domain.getUpdatedBy()) + .build(); + return entity; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/ScreeningScheduleMapper.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/ScreeningScheduleMapper.java new file mode 100644 index 000000000..c36a32a39 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/ScreeningScheduleMapper.java @@ -0,0 +1,31 @@ +package com.hanghae.infrastructure.mapper; + +import com.hanghae.domain.model.ScreeningSchedule; +import com.hanghae.infrastructure.entity.ScreeningScheduleEntity; + +public class ScreeningScheduleMapper { + public static ScreeningSchedule toDomain(ScreeningScheduleEntity entity) { + return new ScreeningSchedule( + entity.getId(), + MovieMapper.toDomain(entity.getMovieEntity()), + ScreenMapper.toDomain(entity.getScreenEntity()), + entity.getShowStartAt(), + entity.getCreatedBy(), + entity.getCreatedAt(), + entity.getUpdatedBy(), + entity.getUpdatedAt() + ); + } + + public static ScreeningScheduleEntity toEntity(ScreeningSchedule domain) { + ScreeningScheduleEntity entity = ScreeningScheduleEntity.builder() + .id(domain.getScheduleId()) + .movieEntity(MovieMapper.toEntity(domain.getMovie())) + .screenEntity(ScreenMapper.toEntity(domain.getScreen())) + .showStartAt(domain.getShowStartAt()) + .createdBy(domain.getCreatedBy()) + .updatedBy(domain.getUpdatedBy()) + .build(); + return entity; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/UploadFileMapper.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/UploadFileMapper.java new file mode 100644 index 000000000..fdb2d4c11 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/mapper/UploadFileMapper.java @@ -0,0 +1,31 @@ +package com.hanghae.infrastructure.mapper; + +import com.hanghae.domain.model.UploadFile; +import com.hanghae.infrastructure.entity.UploadFileEntity; + +public class UploadFileMapper { + public static UploadFile toDomain(UploadFileEntity uploadFileEntity) { + return new UploadFile( + uploadFileEntity.getId(), + uploadFileEntity.getFilePath(), + uploadFileEntity.getFileName(), + uploadFileEntity.getOriginFileName(), + uploadFileEntity.getCreatedBy(), + uploadFileEntity.getCreatedAt(), + uploadFileEntity.getUpdatedBy(), + uploadFileEntity.getUpdatedAt() + ); + } + + public static UploadFileEntity toEntity(UploadFile uploadFile) { + UploadFileEntity uploadFileEntity = UploadFileEntity.builder() + .id(uploadFile.getFileId()) + .filePath(uploadFile.getFilePath()) + .fileName(uploadFile.getFileName()) + .originFileName(uploadFile.getOriginFileName()) + .createdBy(uploadFile.getCreatedBy()) + .updatedBy(uploadFile.getUpdatedBy()) + .build(); + return uploadFileEntity; + } +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/MovieRepositoryJpa.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/MovieRepositoryJpa.java new file mode 100644 index 000000000..4631d3acc --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/MovieRepositoryJpa.java @@ -0,0 +1,7 @@ +package com.hanghae.infrastructure.repository; + +import com.hanghae.infrastructure.entity.MovieEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MovieRepositoryJpa extends JpaRepository { +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/ScreenRepositoryJpa.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/ScreenRepositoryJpa.java new file mode 100644 index 000000000..868033618 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/ScreenRepositoryJpa.java @@ -0,0 +1,7 @@ +package com.hanghae.infrastructure.repository; + +import com.hanghae.infrastructure.entity.ScreenEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScreenRepositoryJpa extends JpaRepository { +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/ScreeningScheduleRepositoryJpa.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/ScreeningScheduleRepositoryJpa.java new file mode 100644 index 000000000..a37647a0e --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/ScreeningScheduleRepositoryJpa.java @@ -0,0 +1,7 @@ +package com.hanghae.infrastructure.repository; + +import com.hanghae.infrastructure.entity.ScreeningScheduleEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScreeningScheduleRepositoryJpa extends JpaRepository { +} diff --git a/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/UploadFileRepositoryJpa.java b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/UploadFileRepositoryJpa.java new file mode 100644 index 000000000..f9b85a053 --- /dev/null +++ b/cinema-infrastructure/src/main/java/com/hanghae/infrastructure/repository/UploadFileRepositoryJpa.java @@ -0,0 +1,7 @@ +package com.hanghae.infrastructure.repository; + +import com.hanghae.infrastructure.entity.UploadFileEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UploadFileRepositoryJpa extends JpaRepository { +} diff --git a/cinema-infrastructure/src/main/resources/application-test.properties b/cinema-infrastructure/src/main/resources/application-test.properties new file mode 100644 index 000000000..d9fb74e70 --- /dev/null +++ b/cinema-infrastructure/src/main/resources/application-test.properties @@ -0,0 +1,25 @@ +# H2 ?????? ?? +# (DB_CLOSE_DELAY=-1: ??? ??? ?? ???? ?? , MODE=MySQL:mysql?? ??) +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=test_user +spring.datasource.password=test_password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +# h2 ?? ?? (http://localhost:8080/h2-console) +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console + +# JPA ?? DDL ?? +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.generate-ddl=true +spring.jpa.open-in-view=false + +# log +#logging.level.root=info + + + diff --git a/cinema-infrastructure/src/main/resources/sql/ScreeningScheduleTest.sql b/cinema-infrastructure/src/main/resources/sql/ScreeningScheduleTest.sql new file mode 100644 index 000000000..3f83b0aaa --- /dev/null +++ b/cinema-infrastructure/src/main/resources/sql/ScreeningScheduleTest.sql @@ -0,0 +1,8 @@ +INSERT INTO upload_file (file_id, file_path, file_name, origin_file_name, created_by, created_at) +VALUES (1, '/radis/test/', 'test.png', 'origin.png', 99, NOW()); + +INSERT INTO movie (movie_id, file_id, title, rating, release_date, running_time_minutes ,genre, created_by, created_at) +VALUES (1, 1, '치토스', '2', '2025-01-01', 90, 'F', 99, NOW()); + +INSERT INTO screen (screen_id, screen_name, created_by, created_at) +VALUES (1, '1관', 99, NOW()); diff --git a/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/TestConfiguration.java b/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/TestConfiguration.java new file mode 100644 index 000000000..908aa5edf --- /dev/null +++ b/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/TestConfiguration.java @@ -0,0 +1,11 @@ +package com.hanghae.infrastructure; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestConfiguration { + /** + * 어뎁터에서 @SpringBootApplication 를 가진 main 메서드를 가지고 있기 떄문에 + * 테스트시 실행을 위해 테스트용 SpringBootApplication 생성 + */ +} diff --git a/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/repository/MovieJpaRepositoryTest.java b/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/repository/MovieJpaRepositoryTest.java new file mode 100644 index 000000000..a059a271f --- /dev/null +++ b/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/repository/MovieJpaRepositoryTest.java @@ -0,0 +1,46 @@ +package com.hanghae.infrastructure.repository; + +import com.hanghae.domain.model.enums.MovieGenre; +import com.hanghae.domain.model.enums.MovieRating; +import com.hanghae.infrastructure.entity.MovieEntity; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DataJpaTest +@ActiveProfiles("test") // application-test.properties 사용 +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class MovieJpaRepositoryTest { + + @Autowired + private MovieRepositoryJpa repository; + + @Test + @DisplayName("영화 조회") + void testSaveAndFindById() { + // given + MovieEntity movie = MovieEntity.builder() + .title("테스트제목") + .rating(MovieRating.ALL) + .releaseDate(LocalDate.of(2025, 01, 10)) + .runningTimeMinutes(11L) + .genre(MovieGenre.ACTION) + .build(); + + // when + MovieEntity savedMovie = repository.save(movie); + List findMovie = repository.findAll(); + + // then + //assertThat(findMovie).isPresent(); + assertThat(findMovie.get(0).getTitle()).isEqualTo("테스트제목"); + } +} diff --git a/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/repository/ScreeningScheduleRepositoryTest.java b/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/repository/ScreeningScheduleRepositoryTest.java new file mode 100644 index 000000000..7306725ca --- /dev/null +++ b/cinema-infrastructure/src/test/java/com/hanghae/infrastructure/repository/ScreeningScheduleRepositoryTest.java @@ -0,0 +1,57 @@ +package com.hanghae.infrastructure.repository; + +import com.hanghae.infrastructure.entity.MovieEntity; +import com.hanghae.infrastructure.entity.ScreenEntity; +import com.hanghae.infrastructure.entity.ScreeningScheduleEntity; +import jakarta.persistence.EntityNotFoundException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DataJpaTest +@ActiveProfiles("test") // application-test.properties 사용 +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class ScreeningScheduleRepositoryTest { + @Autowired + private MovieRepositoryJpa repository; + + @Autowired + private ScreenRepositoryJpa repository2; + + @Autowired + private ScreeningScheduleRepositoryJpa repository3; + + @Test + @Sql(scripts = "/sql/ScreeningScheduleTest.sql") // SQL 파일 실행 + @DisplayName("상영시간표 조회") + void testSaveAndFindAll() { + // given + MovieEntity movie = repository.findById(1L).orElseThrow(()->new EntityNotFoundException("영화 조회 실패")); + ScreenEntity screen = repository2.findById(1L).orElseThrow(()->new EntityNotFoundException("영화 조회 실패")); + + ScreeningScheduleEntity screeningScheduleEntity = ScreeningScheduleEntity.builder() + .screenEntity(screen) + .movieEntity(movie) + .showStartAt(LocalDateTime.of(2024, 10, 15, 11, 11, 11)) + .build(); + + // when + ScreeningScheduleEntity savedScreeningSchedule = repository3.save(screeningScheduleEntity); + List scheduleList = repository3.findAll(); + + // then + assertTrue(scheduleList.size() > 0, "조회된 데이터가 없습니다."); + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..d4e19e9c2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + db: + image: mysql:8.0 # 사용할 도커 이미지, 버전을 명시하는 것이 좋다고함 + # restart: always # 컨테이너가 종료되면 자동 재시작 + container_name: mysql_cinema # 생성될 컨테이너 이름, 작성안하면 임의로 지정됨 + environment: # 컨테이너에 전달할 환경 변수 설정, + # 아래는 초기 컨테이너에 새로운 데이터베이스와 사용자 계정을 자동으로 생성하기 위한 정보 + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} # 루트 비밀번호 + MYSQL_DATABASE: ${MYSQL_DATABASE} # 초기 생성할 데이터베이스 이름 + MYSQL_USER: ${MYSQL_USER} # 초기 생성할 사용자 이름 + MYSQL_PASSWORD: ${MYSQL_PASSWORD} # 초기 생성할 비밀번호 + TZ: Asia/Seoul # Timezone 설정 + ports: # 호스트와 컨테이너 간의 포트를 매핑 + - "3307:3306" # [호스트 포트]:[컨테이너 포트] + # 예) "8880:3306" < 이렇게 설정되었을때 localhost:8880 호출시 컨테이너 내부 3306포트 연결 + volumes: #volumes를 설정하지 않으면 MySQL 컨테이너가 종료되거나 삭제될 때 컨테이너 내부의 모든 데이터가 삭제 + - ./cinema_mysql_data:/var/lib/mysql # 데이터 영구 저장 (호스트 디렉터리 마운트, 개발에 적합) + #- cinema_mysql_data:/var/lib/mysql # 데이터 영구 저장 (명명된 볼륨, 운영에 적합), 이 설정을 쓰면 루트 volumes를 설정해 줘야댐 + command: [ + "--character-set-server=utf8mb4", # MySQL 서버 기본 문자 집합을 utf8mb4로 설정 + "--collation-server=utf8mb4_general_ci", # MySQL 서버 기본 정렬 규칙을 utf8mb4_general_ci로 설정 + "--default-time-zone=Asia/Seoul" # MySQL 서버 기본 타임존을 KST(+09:00)로 설정 + ] + +#volumes : #루트 레벨에 volumes는 Docker가 관리하는 명명된 볼륨을 정의 + #cinema_mysql_data: #섹션에 정의된 볼륨은 여러 서비스간에 공유 가능, services 내부의 volumes가 루트 레벨 volumes를 참조 \ No newline at end of file diff --git a/docs/cineam_create.sql b/docs/cineam_create.sql new file mode 100644 index 000000000..00ee80d92 --- /dev/null +++ b/docs/cineam_create.sql @@ -0,0 +1,73 @@ +CREATE TABLE movie ( + movie_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '영화 ID', + file_id INT UNSIGNED NULL COMMENT '파일 ID (썸네일)', + title varchar(30) NOT NULL COMMENT '영화 제목', + rating varchar(2) NOT NULL DEFAULT 'A' COMMENT '영상물 등급(enum 사용)', + release_date date NULL COMMENT '개봉일', + running_time_minutes INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '러닝타임(분)', + genre varchar(2) NOT NULL DEFAULT '1' COMMENT '영화 장르(enum 사용)', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); + +CREATE TABLE screen ( + screen_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '상영관 ID', + screen_name varchar(20) NULL COMMENT '상영관 이름', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); + +CREATE TABLE screening_schedule ( + schedule_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '상영시간표 ID', + movie_id INT UNSIGNED NOT NULL COMMENT '영화 ID', + screen_id INT UNSIGNED NOT NULL COMMENT '싱영관 ID', + show_start_at datetime NULL COMMENT '상영시작시간', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); + +CREATE TABLE member ( + member_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '회원 ID', + birth_date date NOT NULL COMMENT '생년월일', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); + +CREATE TABLE ticket_reservation ( + ticket_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '영화예매 ID', + schedule_id INT UNSIGNED NOT NULL COMMENT '상영시간표 ID', + seat_id INT UNSIGNED NOT NULL COMMENT '좌석 ID', + member_id INT UNSIGNED NOT NULL COMMENT '회원 ID', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); + +CREATE TABLE screen_seat ( + seat_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '좌석 ID', + seat_name varchar(2) NULL COMMENT '좌석 이름', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); + +CREATE TABLE upload_file ( + file_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '파일 ID', + file_path varchar(100) NULL COMMENT '파일 경로', + file_name varchar(50) NULL COMMENT '파일 이름', + origin_file_name varchar(50) NULL COMMENT '원본 파일 이름', + created_by INT UNSIGNED NULL COMMENT '작성자', + created_at datetime NULL COMMENT '작성일', + updated_by INT UNSIGNED NULL COMMENT '수정자', + updated_at datetime NULL COMMENT '수정일' +); diff --git a/docs/cinema_erd.png b/docs/cinema_erd.png new file mode 100644 index 000000000..cb2ecd04e Binary files /dev/null and b/docs/cinema_erd.png differ diff --git a/docs/init_insert.sql b/docs/init_insert.sql new file mode 100644 index 000000000..93cbffef1 --- /dev/null +++ b/docs/init_insert.sql @@ -0,0 +1,26 @@ +INSERT INTO upload_file (file_path, file_name, origin_file_name, created_by, created_at) +VALUES ('/radis/test/', 'test.png', 'origin.png', 99, NOW()); + +INSERT INTO movie (file_id, title, rating, release_date, running_time_minutes ,genre, created_by, created_at) +VALUES (1, '치토스', '2', '2025-01-01', 90, 'T', 99, NOW()); + +INSERT INTO movie (file_id, title, rating, release_date, running_time_minutes ,genre, created_by, created_at) +VALUES (1, '칸초', '1', '2025-01-02', 120, 'F', 99, NOW()); + +INSERT INTO movie (file_id, title, rating, release_date, running_time_minutes ,genre, created_by, created_at) +VALUES (1, '공공칠빵', '3', '2025-01-10', 110, 'A', 99, NOW()); + +INSERT INTO screen (screen_name, created_by, created_at) +VALUES ('1관', 99, NOW()); + +INSERT INTO screen (screen_name, created_by, created_at) +VALUES ('2관', 99, NOW()); + +INSERT INTO screening_schedule (movie_id, screen_id, show_start_at, created_by, created_at) +VALUES (1, 1, '2025-01-13 10:50:00', 99, NOW()); + +INSERT INTO screening_schedule (movie_id, screen_id, show_start_at, created_by, created_at) +VALUES (2, 2, '2025-01-13 12:00:00', 99, NOW()); + +INSERT INTO screening_schedule (movie_id, screen_id, show_start_at, created_by, created_at) +VALUES (3, 1, '2025-01-13 13:20:00', 99, NOW()); \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..a4b76b953 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..e2847c820 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 000000000..f5feea6d6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..9d21a2183 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..2cf29e7e7 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +rootProject.name = "cinema" +include("cinema-domain") +include("cinema-application") +include("cinema-infrastructure") +include("cinema-adapter")