diff --git a/.gitignore b/.gitignore index 549e00a..97f3b25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ ### STS ### .apt_generated @@ -24,10 +20,7 @@ target/ /nbbuild/ /dist/ /nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ ### VS Code ### .vscode/ +/logs/ diff --git a/bash_scripts/app_preview.bash b/bash_scripts/app_preview.bash new file mode 100644 index 0000000..f58e5cc --- /dev/null +++ b/bash_scripts/app_preview.bash @@ -0,0 +1,50 @@ +#!/bin/bash + +BASE_URL="http://localhost:8080" + +#values used in the requests +DATES=("2023-11-04T04:15:00" "2023-11-10T11:00:00") +SCREENING_IDS=(1 3) +POST_DATA_FILES=("./post_jsons/json_ticket1.json" "./post_jsons/json_ticket2.json") + +HEADERS="-H 'Content-Type:application/json'" + +for ((i=0; i<${#DATES[@]}; i++)); do + # Make a GET request to the "/screening/find-screenings/{date}" endpoint + DATE="${DATES[i]}" + DATE_ENDPOINT="/screening/find-screenings/$DATE" + DATE_URL="$BASE_URL$DATE_ENDPOINT" + curl -X GET $HEADERS "$DATE_URL" + if [ $? -eq 0 ]; then + echo "GET request to $DATE_URL was successful." + else + echo "GET request to $DATE_URL failed." + exit 1 + fi + + # Make a GET request to the "/seat/get-seats/{screeningId}" endpoint + SCREENING_ID="${SCREENING_IDS[i]}" + SEAT_ENDPOINT="/seat/get-seats/$SCREENING_ID" + SEAT_URL="$BASE_URL$SEAT_ENDPOINT" + curl -X GET $HEADERS "$SEAT_URL" + if [ $? -eq 0 ]; then + echo "GET request to $SEAT_URL was successful." + else + echo "GET request to $SEAT_URL failed." + exit 1 + fi + + # Make a POST request to the "/tickets/book-tickets/" endpoint with JSON data from the specified file + POST_DATA_FILE="${POST_DATA_FILES[i]}" + POST_ENDPOINT="/tickets/book-tickets/" + POST_URL="$BASE_URL$POST_ENDPOINT" + curl -X POST $HEADERS -d "@$POST_DATA_FILE" "$POST_URL" + if [ $? -eq 0 ]; then + echo "POST request to $POST_URL was successful." + else + echo "POST request to $POST_URL failed." + exit 1 + fi +done + +echo "All requests completed." diff --git a/bash_scripts/db_setup.bash b/bash_scripts/db_setup.bash new file mode 100644 index 0000000..981347b --- /dev/null +++ b/bash_scripts/db_setup.bash @@ -0,0 +1,41 @@ +#!/bin/bash + +CONTAINER_NAME="mysql-container-nussknacker" + +DB_USER="cinema-app" +DB_PASSWORD="EPqKjMPYhwJIeWKVRVQ7" +DB_NAME="cinema" + +SQL_SCRIPTS_DIR="../sql_scripts" + +# Check if the Docker container is already running +if [ ! "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then + # Run the MySQL Docker container if it's not already running + docker run --ulimit nofile=6400:6400 -d --name "$CONTAINER_NAME" -e MYSQL_ROOT_PASSWORD="$DB_PASSWORD" -e MYSQL_DATABASE="$DB_NAME" -e MYSQL_USER="$DB_USER" -e MYSQL_PASSWORD="$DB_PASSWORD" -p 3306:3306 mysql:8.0 + + # Wait for the MySQL container to be up and running (adjust the timeout as needed) + timeout=60 + while ! docker exec "$CONTAINER_NAME" mysql -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" &> /dev/null; do + timeout=$((timeout - 1)) + if [ "$timeout" -le 0 ]; then + echo "Timeout waiting for MySQL container to start." + exit 1 + fi + sleep 5 + done +fi + +for SCRIPT in "$SQL_SCRIPTS_DIR"/*.sql; do + SCRIPT_NAME=$(basename "$SCRIPT") + + docker exec -i "$CONTAINER_NAME" mysql -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < "$SCRIPT" + + if [ $? -eq 0 ]; then + echo "Script $SCRIPT_NAME executed successfully." + else + echo "Error executing script $SCRIPT_NAME." + exit 1 + fi +done + +echo "All SQL scripts executed successfully." diff --git a/bash_scripts/post_jsons/json_ticket1.json b/bash_scripts/post_jsons/json_ticket1.json new file mode 100644 index 0000000..62f18e1 --- /dev/null +++ b/bash_scripts/post_jsons/json_ticket1.json @@ -0,0 +1,18 @@ +[{ + "name":"John", + "surname":"Doe", + "screeningId":1, + "screeningDate":"2023-11-04T04:15:00", + "seatId":1, + "type":"ADULT" +}, + { + "name":"Andrzej", + "surname":"Brzęczyszczykiewicz", + "screeningId":1, + "screeningDate":"2023-11-04T04:15:00", + "seatId":2, + "type":"CHILD" + } +] + diff --git a/bash_scripts/post_jsons/json_ticket2.json b/bash_scripts/post_jsons/json_ticket2.json new file mode 100644 index 0000000..ab3ccac --- /dev/null +++ b/bash_scripts/post_jsons/json_ticket2.json @@ -0,0 +1,8 @@ +[{ + "name":"John", + "surname":"Doe", + "screeningId":3, + "screeningDate":"2023-11-10T11:00:00", + "seatId":11, + "type":"ADULT" +}] \ No newline at end of file diff --git a/how_to_run.txt b/how_to_run.txt new file mode 100644 index 0000000..7100df4 --- /dev/null +++ b/how_to_run.txt @@ -0,0 +1,17 @@ +To run the app: + +Please install docker, java 21, maven. + +Go to /bash_scripts. +First run the db_setup.bash script - it should start a docker container with mysql 8.0 running. The container is +scheduled for 3306 port (default docker). Then the script will attempt to populate the db with initial tables and values. +If everything is ok, "All SQL scripts executed successfully." Should be printed. + +If the docker container runs properly and the db is filled, run (consider a detached terminal) +mvn spring-boot::run +in the repo directory, it should start the app on localhost:8080. + +For a live use case, please run app_preview.bash - it will run two sequences of how the app could be used. If everything +is ok, the respective curl requests and responses should appear, ending with "All requests completed." + +Feel free to fiddle around with the app, hopefully you break something :) \ No newline at end of file diff --git a/pom.xml b/pom.xml index c071788..2687b34 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,25 @@ org.springframework.boot spring-boot-starter-web + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.springframework.boot + spring-boot-starter-actuator + + + io.springfox + springfox-boot-starter + 3.0.0 + + + org.springframework.boot + spring-boot-starter-validation + 3.1.5 + com.mysql mysql-connector-j @@ -41,7 +59,21 @@ spring-boot-starter-test test - + + org.springframework.data + spring-data-jpa + 3.1.4 + + + com.h2database + h2 + + + junit + junit + test + + diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..cb74a43 --- /dev/null +++ b/readme.txt @@ -0,0 +1,69 @@ + About the app: + +I have treated this task more as a prototype app than production-grade code. Following this philosophy, I wanted to +have a cleanly designed app that is decoupled and open for extension, but only provides minimal, required, functionality. + +This approach is also applicable to testing - I have written some sanity tests for the main logic, places where stuff can go wrong +and some integration tests for the database. I've tried to come up with test cases that could break the app, +but please don't expect 100% coverage and a bulletproof app, as that would take a lot of time and I felt it wouldn't +really make sense. + +I implemented all the functionalities required in the pdf file (TouK_recruitment_task_ticket_booking_app_basic). +I assumed that reserving tickets is different from buying tickets, so the app provides 3 states for a seat - +AVAILABLE, RESERVED, BOUGHT. Reservations are valid for 24 hours. Every day at 2AM, a task runner deletes outdated +reservations from the database and frees the reserved seats. + +As for the database - I have used a relational db (MySQL, but this is no personal preference). The entities are pretty +heavily constrained and I don't think there's much redundancy, but some logic is not enforced by the db - for instance, +there is a RoomRowEntity which has a numberOfSeats field, but the number of actual seats referencing that RoomRow isn't +bound by that value. In a production app this should of course be changed. I've put some indexes on columns that are +frequently looked up. + + + Usage: +The app exposes 3 rest endpoints - + +/screening/find-screenings/{date} - get +/seat/get-seats/{screeningId} - get +/tickets/book-tickets/ - post + +The user calls the screenings endpoint with a date and time and receives a sorted list of screenings in the time period +(date - 30mins, date + 4h). + +The user then calls the /get-seats/ endpoint with a particular screeningId and receives a list of seats for this particular +screening - as there cannot be a single place left over in a row between two already reserved places, the user receives +full list - available and unavailable seats. + +The user then calls the post /book-tickets/ endpoint with a json list of tickets. Tickets are of the format: +{ + "name":"John", + "surname":"Doe", + "screeningId":1, + "screeningDate":"2023-11-02 04:15:00", + "seatId":1, + "type":"ADULT" +} + +I have wondered a bit about how to realize that part - I've thought about implementing some simple session mechanism or mock +that would enable the user to book tickets in a timeframe, but, as the pdf requirements stated, operations were to be +exposed as REST, so that would be in conflict. It would also take a significant amount of work to provide some security +user-based session mechanisms and test them. In the end I've chosen a completely stateless approach where the whole +request is processed in one server call. + +As the business use case states that the user chooses a particular screening, and I think it makes sense to only book for +one screening, all tickets have to be for the same screening. + +Tickets can have different holders - it makes sense in a real world scenario (someone can pick up his ticket without +the person making the reservation) and was cleaner to implement :). + +The holder's name and surname should obey the specification provided in the .pdf file. If the surname is two-part, +dashed, both parts must obey the minimum 3 letter length and other rules. + +If the tickets are valid, the system logs them into the db, stamps a reservation expiration date and returns a .json +Reservation object of the form List, ticketCostSum, expirationDate. + +I've also provided a +screenings/find-screenings/{movie}/{date} - get endpoint +to find screenings of a particular movie, seemed natural, but I didn't test that very much. + +Please read how_to_run.txt for running manual. diff --git a/sql_scripts/create_schemas.sql b/sql_scripts/create_schemas.sql new file mode 100644 index 0000000..125199c --- /dev/null +++ b/sql_scripts/create_schemas.sql @@ -0,0 +1,147 @@ +-- MySQL Workbench Forward Engineering + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + +-- ----------------------------------------------------- +-- Schema mydb +-- ----------------------------------------------------- +-- ----------------------------------------------------- +-- Schema cinema +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema cinema +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS `cinema` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ; +USE `cinema` ; + +-- ----------------------------------------------------- +-- Table `cinema`.`movies` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `cinema`.`movies` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `name` VARCHAR(255) NULL DEFAULT NULL, + `run_time_in_seconds` BIGINT NOT NULL, + PRIMARY KEY (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 4 +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_unicode_ci; + + +-- ----------------------------------------------------- +-- Table `cinema`.`rooms` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `cinema`.`rooms` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `number_of_rows` INT NOT NULL, + PRIMARY KEY (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 4 +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_unicode_ci; + + +-- ----------------------------------------------------- +-- Table `cinema`.`room_rows` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `cinema`.`room_rows` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `seats_in_row` INT NOT NULL, + `cinema_room_id` BIGINT NULL DEFAULT NULL, + PRIMARY KEY (`id`), + INDEX `FKjf5n9bpuirq7ft8xtwlli67hk` (`cinema_room_id` ASC) VISIBLE, + CONSTRAINT `FKjf5n9bpuirq7ft8xtwlli67hk` + FOREIGN KEY (`cinema_room_id`) + REFERENCES `cinema`.`rooms` (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 7 +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_unicode_ci; + + +-- ----------------------------------------------------- +-- Table `cinema`.`screenings` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `cinema`.`screenings` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `screening_start_time` TIMESTAMP NULL DEFAULT NULL, + `movie_id` BIGINT NULL DEFAULT NULL, + `room_id` BIGINT NULL DEFAULT NULL, + PRIMARY KEY (`id`), + INDEX `FKrnko8743nv2o7jd7ix2wtcyf` (`movie_id` ASC) VISIBLE, + INDEX `FKq9rxs70hfk4yejjiqw86cxj6t` (`room_id` ASC) VISIBLE, + CONSTRAINT `FKq9rxs70hfk4yejjiqw86cxj6t` + FOREIGN KEY (`room_id`) + REFERENCES `cinema`.`rooms` (`id`), + CONSTRAINT `FKrnko8743nv2o7jd7ix2wtcyf` + FOREIGN KEY (`movie_id`) + REFERENCES `cinema`.`movies` (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 8 +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_unicode_ci; + + +-- ----------------------------------------------------- +-- Table `cinema`.`seats` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `cinema`.`seats` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `is_occupied` BIT(1) NOT NULL, + `seat_number` INT NOT NULL, + `status` ENUM('AVAILABLE', 'BOUGHT', 'RESERVED') NULL DEFAULT NULL, + `room_id` BIGINT NULL DEFAULT NULL, + `room_row_id` BIGINT NULL DEFAULT NULL, + `screening_id` BIGINT NULL DEFAULT NULL, + PRIMARY KEY (`id`), + INDEX `FKg993pi7ucgy616icmddq8u335` (`room_id` ASC) VISIBLE, + INDEX `FKi3680iwv3olco0f4ham6idr4u` (`room_row_id` ASC) VISIBLE, + INDEX `FKlply6gd8uk8rrqr1muf7s8hn` (`screening_id` ASC) VISIBLE, + CONSTRAINT `FKg993pi7ucgy616icmddq8u335` + FOREIGN KEY (`room_id`) + REFERENCES `cinema`.`rooms` (`id`), + CONSTRAINT `FKi3680iwv3olco0f4ham6idr4u` + FOREIGN KEY (`room_row_id`) + REFERENCES `cinema`.`room_rows` (`id`), + CONSTRAINT `FKlply6gd8uk8rrqr1muf7s8hn` + FOREIGN KEY (`screening_id`) + REFERENCES `cinema`.`screenings` (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 27 +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_unicode_ci; + + +-- ----------------------------------------------------- +-- Table `cinema`.`tickets` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `cinema`.`tickets` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `reservation_expiration` TIMESTAMP NULL DEFAULT NULL, + `name` VARCHAR(255) NULL DEFAULT NULL, + `screening_start_time` TIMESTAMP NULL DEFAULT NULL, + `surname` VARCHAR(255) NULL DEFAULT NULL, + `type` TINYINT NULL DEFAULT NULL, + `screening_id` BIGINT NULL DEFAULT NULL, + `seat_id` BIGINT NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `UK_dvmspsturfwlx8cf8f3ewn87c` (`seat_id` ASC) VISIBLE, + INDEX `FKa8cgc3b3atbn8sr3nebdygo4a` (`screening_id` ASC) VISIBLE, + CONSTRAINT `FK1f6n3pv4b80wl6gj4ra32ctxk` + FOREIGN KEY (`seat_id`) + REFERENCES `cinema`.`seats` (`id`), + CONSTRAINT `FKa8cgc3b3atbn8sr3nebdygo4a` + FOREIGN KEY (`screening_id`) + REFERENCES `cinema`.`screenings` (`id`)) +ENGINE = InnoDB +AUTO_INCREMENT = 9 +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_unicode_ci; + + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/sql_scripts/import_movies.sql b/sql_scripts/import_movies.sql new file mode 100644 index 0000000..a627fb1 --- /dev/null +++ b/sql_scripts/import_movies.sql @@ -0,0 +1,3 @@ +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (1, 'Star Wars', 3600); +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (2, 'Captain Hook', 5400); +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (3, 'Mis', 7200); \ No newline at end of file diff --git a/sql_scripts/import_rooms.sql b/sql_scripts/import_rooms.sql new file mode 100644 index 0000000..a2a6802 --- /dev/null +++ b/sql_scripts/import_rooms.sql @@ -0,0 +1,6 @@ +INSERT INTO rooms (id, number_of_rows) VALUES +(1, 2), +(2, 2), +(3,2); + + diff --git a/sql_scripts/import_screenings.sql b/sql_scripts/import_screenings.sql new file mode 100644 index 0000000..755f202 --- /dev/null +++ b/sql_scripts/import_screenings.sql @@ -0,0 +1,9 @@ +INSERT INTO screenings (id, movie_id, room_id, screening_start_time) VALUES +(1, 1, 1, '2023-11-04 04:15:00'), +(2, 2, 2, '2023-11-03 11:00:00'), +(3, 2, 3, '2023-11-10 11:00:00'), +(4, 3, 1, '2023-11-25 06:15:00'), +(5, 1, 2, '2023-10-29 06:15:00'), +(6, 3, 3, '2023-11-03 06:15:00'), +(7, 2, 3, '2023-11-04 04:15:00'); + diff --git a/sql_scripts/import_seats.sql b/sql_scripts/import_seats.sql new file mode 100644 index 0000000..0b52cc0 --- /dev/null +++ b/sql_scripts/import_seats.sql @@ -0,0 +1,57 @@ +-- Populate the room-rows table with test values and specific IDs +INSERT INTO room_rows (id, seats_in_row, cinema_room_id) VALUES +-- room 1 +(1, 3, 1), +(2, 3, 1), + +-- room 2 +(3,2,2), +(4,2,2), + +-- room 3 +(5, 1, 3), +(6, 2, 3); + + +-- Populate the seats table with test values and specific IDs +INSERT INTO seats (id, room_id, room_row_id, seat_number, screening_id, is_occupied, status) VALUES +-- room 1 screening 1 +(1, 1, 1, 1, 1, false, 'AVAILABLE'), +(2, 1, 1, 2, 1, false, 'AVAILABLE'), +(3, 1, 1, 3, 1, true, 'BOUGHT'), +(4, 1, 2, 1, 1, false, 'AVAILABLE'), +(5, 1, 2, 2, 1, false, 'AVAILABLE'), +(6, 1, 2, 3, 1, true, 'BOUGHT'), + +-- room 1 screening 4 +(14, 1, 1, 1, 4, false, 'AVAILABLE'), +(15, 1, 1, 2, 4, false, 'AVAILABLE'), +(16, 1, 1, 3, 4, true, 'BOUGHT'), +(17, 1, 2, 1, 4, false, 'AVAILABLE'), +(18, 1, 2, 2, 4, false, 'AVAILABLE'), +(19, 1, 2, 3, 4, true, 'BOUGHT'), + +-- room 2 screening 2 +(7, 2, 3, 2, 2, false, 'AVAILABLE'), +(8, 2, 3, 2, 2, true, 'BOUGHT'), +(9, 2, 4, 1, 2, false, 'AVAILABLE'), +(10, 2, 4, 2, 2, false, 'AVAILABLE'), + +-- room 2 screening 5 +(20, 2, 3, 1, 5, false, 'AVAILABLE'), +(21, 2, 3, 2, 5, false, 'AVAILABLE'), +(22, 2, 4, 1, 5, true, 'BOUGHT'), +(23, 2, 4, 2, 5, false, 'AVAILABLE'), + +-- room 3 screening 3 +(11, 3, 5, 1, 3, false, 'AVAILABLE'), +(12, 3, 6, 2, 3, true, 'BOUGHT'), +(13, 3, 6, 1, 3, false, 'AVAILABLE'), + +-- room 3 screening 6 +(24, 3, 5, 1, 6, true, 'BOUGHT'), +(25, 3, 6, 2, 6, false, 'AVAILABLE'), +(26, 3, 6, 1, 6, false, 'AVAILABLE'); + + + diff --git a/src/main/java/StaticController.java b/src/main/java/StaticController.java deleted file mode 100644 index 4d0edc3..0000000 --- a/src/main/java/StaticController.java +++ /dev/null @@ -1,12 +0,0 @@ -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Controller -@RequestMapping("/static") -public class StaticController { - - public String welcomePage(){ - return "welcome"; - } -} diff --git a/src/main/java/com/ramcel/cinema/reservation/Application.java b/src/main/java/com/ramcel/cinema/reservation/Application.java index e74672d..3d74f63 100644 --- a/src/main/java/com/ramcel/cinema/reservation/Application.java +++ b/src/main/java/com/ramcel/cinema/reservation/Application.java @@ -1,13 +1,18 @@ package com.ramcel.cinema.reservation; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ComponentScan; -@SpringBootApplication +import java.util.Arrays; + +@SpringBootApplication() public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); + public static void main(String[] args) {SpringApplication.run(Application.class, args); } } diff --git a/src/main/java/com/ramcel/cinema/reservation/db/entity/BaseEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/BaseEntity.java new file mode 100644 index 0000000..32739ce --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/BaseEntity.java @@ -0,0 +1,21 @@ +package com.ramcel.cinema.reservation.db.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@MappedSuperclass +@AllArgsConstructor +@NoArgsConstructor +public abstract class BaseEntity implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + +} + diff --git a/src/main/java/com/ramcel/cinema/reservation/db/entity/MovieEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/MovieEntity.java new file mode 100644 index 0000000..b213bc8 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/MovieEntity.java @@ -0,0 +1,37 @@ +package com.ramcel.cinema.reservation.db.entity; + +import com.ramcel.cinema.reservation.functionalities.screening.Movie; +import jakarta.persistence.*; +import lombok.*; + +import java.time.Duration; + +@Entity +@Table(name = "movies") +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class MovieEntity extends BaseEntity { + + private String name; + + private long runTimeInSeconds; + + public MovieEntity(String name, long runTimeInSeconds){ + this.name = name; + this.runTimeInSeconds=runTimeInSeconds; + } + + public MovieEntity(String name, String runTime){ + this(name, Integer.parseInt(runTime)); + } + + public Duration getRunTimeDuration(){ + return Duration.ofSeconds(this.runTimeInSeconds); + } + + public Movie mapToMovie(){ + return new Movie(this.name, this.runTimeInSeconds); + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/screening/RoomEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/RoomEntity.java similarity index 53% rename from src/main/java/com/ramcel/cinema/reservation/screening/RoomEntity.java rename to src/main/java/com/ramcel/cinema/reservation/db/entity/RoomEntity.java index b52f4fc..fb32f93 100644 --- a/src/main/java/com/ramcel/cinema/reservation/screening/RoomEntity.java +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/RoomEntity.java @@ -1,23 +1,22 @@ -package com.ramcel.cinema.reservation.screening; +package com.ramcel.cinema.reservation.db.entity; import jakarta.persistence.*; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; import java.util.List; + @Entity -@Data +@Table(name = "rooms") +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor -public class RoomEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; +public class RoomEntity extends BaseEntity{ private int numberOfRows; - @OneToMany(mappedBy = "cinemaRoom") + @OneToMany(mappedBy = "cinemaRoom", fetch = FetchType.EAGER) private List roomRows; public RoomEntity(List roomRows){ diff --git a/src/main/java/com/ramcel/cinema/reservation/db/entity/RoomRowEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/RoomRowEntity.java new file mode 100644 index 0000000..b9161d1 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/RoomRowEntity.java @@ -0,0 +1,32 @@ +package com.ramcel.cinema.reservation.db.entity; + +import jakarta.persistence.*; +import lombok.*; + + +@Entity +@Getter +@Setter +@Table(name = "room_rows") +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class RoomRowEntity extends BaseEntity{ + + private int seatsInRow; + + @ManyToOne + private RoomEntity cinemaRoom; + + public RoomRowEntity(int seatsInRow, RoomEntity cinemaRoom){ + this.cinemaRoom = cinemaRoom; + this.seatsInRow = seatsInRow; + } + + public RoomRowEntity(long id, int seatsInRow, RoomEntity cinemaRoom){ + super(id); + this.cinemaRoom = cinemaRoom; + this.seatsInRow = seatsInRow; + } + + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/db/entity/ScreeningEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/ScreeningEntity.java new file mode 100644 index 0000000..92decc3 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/ScreeningEntity.java @@ -0,0 +1,37 @@ +package com.ramcel.cinema.reservation.db.entity; + +import com.ramcel.cinema.reservation.functionalities.screening.Screening; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + + +@Entity +@Table(name = "screenings") +@EqualsAndHashCode(callSuper = true) +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScreeningEntity extends BaseEntity{ + + @ManyToOne + @JoinColumn(name = "movie_id") + private MovieEntity movieEntity; + + @ManyToOne + @JoinColumn(name = "room_id") + private RoomEntity roomEntity; + + @Column(columnDefinition = "TIMESTAMP", name = "screening_start_time") + private LocalDateTime dateTime; + + public Screening mapToScreening(){ + return new Screening(this.getId(), + this.movieEntity.mapToMovie(), + this.roomEntity.getId(), + this.dateTime + ); + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/db/entity/SeatEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/SeatEntity.java new file mode 100644 index 0000000..9c41f29 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/SeatEntity.java @@ -0,0 +1,68 @@ +package com.ramcel.cinema.reservation.db.entity; + +import com.ramcel.cinema.reservation.functionalities.exception.IllegalSeatException; +import com.ramcel.cinema.reservation.functionalities.seat.Seat; +import com.ramcel.cinema.reservation.functionalities.seat.SeatStatus; +import jakarta.persistence.*; +import lombok.*; + + +@Entity +@Data +@Table(name = "seats") +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class SeatEntity extends BaseEntity { + + + @ManyToOne + @JoinColumn(name = "room_id") + private RoomEntity room; + + @ManyToOne + @JoinColumn(name = "room_row_id") + private RoomRowEntity roomRow; + + private int seatNumber; + + @ManyToOne + @JoinColumn(name = "screening_id") + private ScreeningEntity screening; + + private boolean isOccupied = false; + + @Enumerated(EnumType.STRING) + private SeatStatus status; + + public Seat mapToSeat(){ + return Seat.builder() + .seatId(super.getId()) + .roomId(room.getId()) + .roomRowId(roomRow.getId()) + .seatNumber(seatNumber) + .screeningId(screening.getId()) + .isOccupied(isOccupied) + .status(status) + .build(); + } + + + public void reserve() throws IllegalSeatException { + if(status == SeatStatus.BOUGHT){ + throw new IllegalSeatException("Seat already bought!"); + } + status = SeatStatus.RESERVED; + isOccupied = true; + } + public void buy(){ + status = SeatStatus.BOUGHT; + isOccupied = true; + } + + public void free(){ + status = SeatStatus.AVAILABLE; + isOccupied = false; + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/db/entity/TicketEntity.java b/src/main/java/com/ramcel/cinema/reservation/db/entity/TicketEntity.java new file mode 100644 index 0000000..3183a14 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/entity/TicketEntity.java @@ -0,0 +1,63 @@ +package com.ramcel.cinema.reservation.db.entity; + +import com.ramcel.cinema.reservation.functionalities.seat.SeatStatus; +import com.ramcel.cinema.reservation.functionalities.ticket.TicketType; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + + +@Entity +@Table(name = "tickets") +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TicketEntity extends BaseEntity{ + + private String name; + + private String surname; + + @ManyToOne + @JoinColumn(name = "screening_id") + private ScreeningEntity screening; + + + @Column(name = "screening_start_time", columnDefinition = "TIMESTAMP") + private LocalDateTime screeningDate; + + @OneToOne + @JoinColumn(name = "seat_id") + private SeatEntity seat; + + private TicketType type; + + //pass null if the ticket is not reserved but bought + @Column(name = "reservation_expiration", columnDefinition = "TIMESTAMP") + private LocalDateTime expirationDate; + + @Transient + boolean isReservation; + + @PrePersist + public void prePersist() { + if (expirationDate == null) { + if (isReservation) { + expirationDate = LocalDateTime.now().plusDays(1); + } + } + } + + public void reserve(){ + isReservation = true; + } + + public void buy(){ + isReservation = false; + } + + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/db/repositories/ScreeningRepository.java b/src/main/java/com/ramcel/cinema/reservation/db/repositories/ScreeningRepository.java new file mode 100644 index 0000000..fe0e6ba --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/repositories/ScreeningRepository.java @@ -0,0 +1,20 @@ +package com.ramcel.cinema.reservation.db.repositories; + +import com.ramcel.cinema.reservation.db.entity.ScreeningEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface ScreeningRepository extends JpaRepository { + + @Query("SELECT s FROM ScreeningEntity s WHERE s.dateTime >= :periodStart AND s.dateTime <= :periodEnd") + List findScreeningsInPeriod( + @Param("periodStart") LocalDateTime periodStart, + @Param("periodEnd") LocalDateTime periodEnd + ); +} diff --git a/src/main/java/com/ramcel/cinema/reservation/db/repositories/SeatRepository.java b/src/main/java/com/ramcel/cinema/reservation/db/repositories/SeatRepository.java new file mode 100644 index 0000000..81a3aa6 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/repositories/SeatRepository.java @@ -0,0 +1,24 @@ +package com.ramcel.cinema.reservation.db.repositories; + +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SeatRepository extends JpaRepository { + + @Query("SELECT s FROM SeatEntity s WHERE s.screening.id = :screeningId") + List findSeatByScreeningId( + @Param("screeningId") Long screeningId); + + @Query("SELECT s FROM SeatEntity s WHERE s.roomRow.id = :roomRow AND s.screening.id = :screeningId") + List findSeatsByRoomRowIdAndScreeningID( + @Param("roomRow") Long roomRowId, + @Param("screeningId") Long screeningId); + + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/db/repositories/TicketRepository.java b/src/main/java/com/ramcel/cinema/reservation/db/repositories/TicketRepository.java new file mode 100644 index 0000000..f23f468 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/db/repositories/TicketRepository.java @@ -0,0 +1,15 @@ +package com.ramcel.cinema.reservation.db.repositories; + +import com.ramcel.cinema.reservation.db.entity.TicketEntity; +import com.ramcel.cinema.reservation.functionalities.reservation.Reservation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface TicketRepository extends JpaRepository { + + List findByExpirationDateBefore(LocalDateTime now); +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/ScreeningController.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/ScreeningController.java new file mode 100644 index 0000000..ba1f19a --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/ScreeningController.java @@ -0,0 +1,52 @@ +package com.ramcel.cinema.reservation.functionalities.controllers; + +import com.ramcel.cinema.reservation.functionalities.screening.Movie; +import com.ramcel.cinema.reservation.functionalities.screening.Screening; +import com.ramcel.cinema.reservation.functionalities.screening.ScreeningService; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/screening") +public class ScreeningController { + + @Autowired + private ScreeningService screeningService; + + @GetMapping(value = "/find-screenings/{date}", + produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> findScreenings(@Valid @PathVariable("date") LocalDateTime dateTime){ + List resultList = screeningService.findScreenings(dateTime); + + return getResponse(resultList); + } + + @GetMapping(value = "/find-screenings/{movie}/{date}", + produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> findScreenings(@Valid @PathVariable("movie") Movie movie, @Valid @PathVariable("date") LocalDateTime date){ + List resultList = screeningService.findScreenings(movie, date); + + return getResponse(resultList); + } + + private ResponseEntity> getResponse(List resultList){ + return Optional.of(resultList) + .filter(list -> !list.isEmpty()) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.noContent().build()); + } + + + + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/SeatController.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/SeatController.java new file mode 100644 index 0000000..92247be --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/SeatController.java @@ -0,0 +1,34 @@ +package com.ramcel.cinema.reservation.functionalities.controllers; + +import com.ramcel.cinema.reservation.functionalities.seat.Seat; +import com.ramcel.cinema.reservation.functionalities.seat.SeatService; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/seat") +public class SeatController { + + @Autowired + private SeatService seatService; + + @GetMapping("/get-seats/{screeningId}") + public ResponseEntity> getAvailableSeatsByScreening(@Valid @PathVariable long screeningId) { + List resultList = seatService.getAvailableSeats(screeningId); + return getResponse(resultList); + } + + private ResponseEntity> getResponse(List resultList){ + return Optional.of(resultList) + .filter(list -> !list.isEmpty()) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.noContent().build()); + } +} + + diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/TicketController.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/TicketController.java new file mode 100644 index 0000000..075ca27 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/controllers/TicketController.java @@ -0,0 +1,43 @@ +package com.ramcel.cinema.reservation.functionalities.controllers; + +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalTicketException; +import com.ramcel.cinema.reservation.functionalities.reservation.Reservation; +import com.ramcel.cinema.reservation.functionalities.ticket.Ticket; +import com.ramcel.cinema.reservation.functionalities.ticket.TicketService; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/tickets") +@AllArgsConstructor +public class TicketController { + + @Autowired + TicketService ticketService; + + @PostMapping(value = "/book-tickets/", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity bookTickets(@Valid @RequestBody List ticketList) throws IllegalReservationException{ + return ResponseEntity.ok(ticketService.bookTicket(ticketList)); + } + + @ExceptionHandler(IllegalReservationException.class) + @ResponseStatus(code = HttpStatus.BAD_REQUEST) + public ResponseEntity handleException(IllegalReservationException e){ + return ResponseEntity.badRequest().build(); + } + +} + + diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalReservationException.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalReservationException.java new file mode 100644 index 0000000..ba025ff --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalReservationException.java @@ -0,0 +1,7 @@ +package com.ramcel.cinema.reservation.functionalities.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class IllegalReservationException extends RuntimeException{ +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalSeatException.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalSeatException.java new file mode 100644 index 0000000..ce8b486 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalSeatException.java @@ -0,0 +1,7 @@ +package com.ramcel.cinema.reservation.functionalities.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class IllegalSeatException extends IllegalTicketException{ +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalTicketException.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalTicketException.java new file mode 100644 index 0000000..4ec74a0 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/exception/IllegalTicketException.java @@ -0,0 +1,7 @@ +package com.ramcel.cinema.reservation.functionalities.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class IllegalTicketException extends IllegalReservationException{ +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/reservation/Reservation.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/reservation/Reservation.java new file mode 100644 index 0000000..a4ce630 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/reservation/Reservation.java @@ -0,0 +1,9 @@ +package com.ramcel.cinema.reservation.functionalities.reservation; + +import com.ramcel.cinema.reservation.functionalities.ticket.Ticket; + +import java.time.LocalDateTime; +import java.util.List; + +public record Reservation(ListticketList, java.math.BigDecimal ticketCost, LocalDateTime expirationDate) { +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/reservation/ReservationServiceImpl.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/reservation/ReservationServiceImpl.java new file mode 100644 index 0000000..edcd507 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/reservation/ReservationServiceImpl.java @@ -0,0 +1,37 @@ +package com.ramcel.cinema.reservation.functionalities.reservation; + +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.db.entity.TicketEntity; +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import com.ramcel.cinema.reservation.db.repositories.TicketRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +public class ReservationServiceImpl { + + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private SeatRepository seatRepository; + + @Scheduled(cron = "0 0 2 * * *") // Runs every day at 2:00 AM + public void deleteExpiredReservations() { + LocalDateTime now = LocalDateTime.now(); + List expiredReservations = ticketRepository.findByExpirationDateBefore(now); + freeReservedSeats(expiredReservations); + + ticketRepository.deleteAll(expiredReservations); + } + + private void freeReservedSeats(List expiredReservations) { + List reservedSeats = expiredReservations.stream().map(TicketEntity::getSeat).toList(); + reservedSeats.forEach(SeatEntity::free); + seatRepository.saveAll(reservedSeats); + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Movie.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Movie.java new file mode 100644 index 0000000..e92bd43 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Movie.java @@ -0,0 +1,11 @@ +package com.ramcel.cinema.reservation.functionalities.screening; + + +import com.ramcel.cinema.reservation.db.entity.MovieEntity; + +public record Movie(String name, long runTimeInSeconds) { + + public MovieEntity mapToEntity(){ + return new MovieEntity(name, runTimeInSeconds); + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Room.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Room.java new file mode 100644 index 0000000..468a826 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Room.java @@ -0,0 +1,7 @@ +package com.ramcel.cinema.reservation.functionalities.screening; + +import java.util.List; + +public record Room(long id, int numberOfRows, List roomRows) { + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/RoomRow.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/RoomRow.java new file mode 100644 index 0000000..61f522a --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/RoomRow.java @@ -0,0 +1,6 @@ +package com.ramcel.cinema.reservation.functionalities.screening; + +import com.ramcel.cinema.reservation.db.entity.RoomEntity; + +public record RoomRow(long rowId, int seatsInRow, RoomEntity cinemaRoom) { +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Screening.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Screening.java new file mode 100644 index 0000000..3215911 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/Screening.java @@ -0,0 +1,6 @@ +package com.ramcel.cinema.reservation.functionalities.screening; + +import java.time.LocalDateTime; + +public record Screening(long id, Movie movie, long roomId, LocalDateTime dateTime) { +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/ScreeningService.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/ScreeningService.java new file mode 100644 index 0000000..22854a8 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/ScreeningService.java @@ -0,0 +1,11 @@ +package com.ramcel.cinema.reservation.functionalities.screening; + +import java.time.LocalDateTime; +import java.util.List; + +public interface ScreeningService { + + public List findScreenings(LocalDateTime dateTime); + + public List findScreenings(Movie movie, LocalDateTime dateTime); +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/ScreeningServiceImpl.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/ScreeningServiceImpl.java new file mode 100644 index 0000000..de79555 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/screening/ScreeningServiceImpl.java @@ -0,0 +1,50 @@ +package com.ramcel.cinema.reservation.functionalities.screening; + +import com.ramcel.cinema.reservation.db.entity.ScreeningEntity; +import com.ramcel.cinema.reservation.db.repositories.ScreeningRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; + +@Service +@Slf4j +public class ScreeningServiceImpl implements ScreeningService{ + + @Autowired + private ScreeningRepository screeningRepository; + + @Override + public List findScreenings(LocalDateTime dateTime) { + LocalDateTime searchStartDate = dateTime.minusMinutes(30); + LocalDateTime searchEndDate = dateTime.plusHours(4); + + List foundScreenings = screeningsListInPeriod(searchStartDate, searchEndDate); + + return sortScreeningsByMovieNameAndRunTime(foundScreenings); + } + + private List screeningsListInPeriod(LocalDateTime searchStartDate, LocalDateTime searchEndDate) { + return screeningRepository.findScreeningsInPeriod(searchStartDate, searchEndDate) + .stream().peek(v -> log.atDebug().log("Fetched: " + v.getId())) + .map(ScreeningEntity::mapToScreening).toList(); + } + + @Override + public List findScreenings(Movie movie, LocalDateTime dateTime) { + return findScreenings(dateTime).stream() + .filter(s -> s.movie().equals(movie)) + .toList(); + } + + public static List sortScreeningsByMovieNameAndRunTime(List screenings) { + Comparator comparator = Comparator + .comparing((Screening screening) -> screening.movie().name()) + .thenComparingLong(screening -> screening.movie().runTimeInSeconds()); + + return List.copyOf(screenings).stream().sorted(comparator).toList(); + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/Seat.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/Seat.java new file mode 100644 index 0000000..d785395 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/Seat.java @@ -0,0 +1,26 @@ +package com.ramcel.cinema.reservation.functionalities.seat; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class Seat { + + private final long seatId; + + private final long roomId; + + private final long roomRowId; + + private final int seatNumber; + + private final long screeningId; + + private boolean isOccupied; + + private SeatStatus status; + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatService.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatService.java new file mode 100644 index 0000000..345a517 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatService.java @@ -0,0 +1,7 @@ +package com.ramcel.cinema.reservation.functionalities.seat; + +import java.util.List; + +public interface SeatService { + public List getAvailableSeats(Long screeningId); +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatServiceImpl.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatServiceImpl.java new file mode 100644 index 0000000..5815055 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatServiceImpl.java @@ -0,0 +1,19 @@ +package com.ramcel.cinema.reservation.functionalities.seat; + +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class SeatServiceImpl implements SeatService{ + + @Autowired + private SeatRepository seatRepository; + @Override + public List getAvailableSeats(Long screeningId) { + return seatRepository.findSeatByScreeningId(screeningId).stream().map(SeatEntity::mapToSeat).toList(); + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatStatus.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatStatus.java new file mode 100644 index 0000000..1243da9 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/seat/SeatStatus.java @@ -0,0 +1,6 @@ +package com.ramcel.cinema.reservation.functionalities.seat; + + +public enum SeatStatus { + AVAILABLE, RESERVED, BOUGHT +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/Ticket.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/Ticket.java new file mode 100644 index 0000000..be178d1 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/Ticket.java @@ -0,0 +1,33 @@ +package com.ramcel.cinema.reservation.functionalities.ticket; + +import com.ramcel.cinema.reservation.db.entity.TicketEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class Ticket { + private String name; + + private String surname; + + private long screeningId; + + private LocalDateTime screeningDate; + + private long seatId; + + private TicketType type; + + @Override + public String toString(){ + return "Holder surname: " + surname + " , screening: " + screeningId + " ,seat: " + seatId; + } + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketMapper.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketMapper.java new file mode 100644 index 0000000..c951bb2 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketMapper.java @@ -0,0 +1,55 @@ +package com.ramcel.cinema.reservation.functionalities.ticket; + +import com.ramcel.cinema.reservation.db.entity.ScreeningEntity; +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.db.entity.TicketEntity; +import com.ramcel.cinema.reservation.db.repositories.ScreeningRepository; +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class TicketMapper { + + @Autowired + private SeatRepository seatRepository; + @Autowired + private ScreeningRepository screeningRepository; + + + public TicketEntity mapToEntity(Ticket ticket) throws IllegalArgumentException { + TicketEntity returnEntity = buildBasicEntity(ticket); + + Optional mappedSeat = seatRepository.findById(ticket.getSeatId()); + Optional mappedScreening = screeningRepository.findById(ticket.getScreeningId()); + + if(mappedSeat.isPresent() && mappedScreening.isPresent()){ + returnEntity.setSeat(mappedSeat.get()); + returnEntity.setScreening(mappedScreening.get()); + return returnEntity; + } + else { + throw new IllegalArgumentException("Couldn't find foreign keys in db for screening date:" + + ticket.getScreeningDate() + + " seat: " + + ticket.getSeatId()); + } + } + + private static TicketEntity buildBasicEntity(Ticket ticket) { + return TicketEntity.builder() + .name(ticket.getName()) + .surname(ticket.getSurname()) + .screeningDate(ticket.getScreeningDate()) + .type(ticket.getType()) + .build(); + } + + public TicketEntity mapToReservedEntity(Ticket ticket) throws IllegalArgumentException{ + TicketEntity baseEntity = mapToEntity(ticket); + baseEntity.reserve(); + return baseEntity; + } +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketService.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketService.java new file mode 100644 index 0000000..2c0a3d5 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketService.java @@ -0,0 +1,11 @@ +package com.ramcel.cinema.reservation.functionalities.ticket; + +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.reservation.Reservation; + +import java.util.List; + +public interface TicketService { + public Reservation bookTicket(Ticket ticket) throws IllegalReservationException; + public Reservation bookTicket(List ticketList) throws IllegalReservationException; +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketServiceImpl.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketServiceImpl.java new file mode 100644 index 0000000..8753189 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketServiceImpl.java @@ -0,0 +1,90 @@ +package com.ramcel.cinema.reservation.functionalities.ticket; + +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.db.entity.TicketEntity; +import com.ramcel.cinema.reservation.db.repositories.TicketRepository; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalSeatException; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalTicketException; +import com.ramcel.cinema.reservation.functionalities.reservation.Reservation; +import com.ramcel.cinema.reservation.functionalities.ticket.validators.TicketValidator; +import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +@Service +@AllArgsConstructor +public class TicketServiceImpl implements TicketService { + + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private TicketMapper mapper; + + @Autowired + private TicketValidator validator; + + + @Override + public Reservation bookTicket(Ticket ticket) throws IllegalReservationException { + if (validator.isValid(ticket)) { + return handleReservation(ticket); + } else { + throw new IllegalReservationException("Ticket: " + ticket + " is not valid."); + } + } + + private Reservation handleReservation(Ticket ticket) throws IllegalReservationException{ + manageEntitiesState(ticket); + + LocalDateTime expirationDate = LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES); + BigDecimal price = ticket.getType().getPrice(); + + return new Reservation(List.of(ticket), price, expirationDate); + } + + @Override + public Reservation bookTicket(List ticketList) throws IllegalReservationException { + if (validator.isValid(ticketList)) { + return handleReservation(ticketList); + } else { + throw new IllegalTicketException("Ticket list " + ticketList + " is not valid."); + } + } + + private Reservation handleReservation(List ticketList) throws IllegalReservationException { + ticketList.forEach(this::manageEntitiesState); + + BigDecimal totalPrice = ticketList.stream() + .map(t -> t.getType().getPrice()) + .reduce(BigDecimal::add).orElseThrow(IllegalArgumentException::new); + + LocalDateTime expirationDate = LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES); + + return new Reservation(ticketList, totalPrice, expirationDate); + } + + private void manageEntitiesState(Ticket ticket) throws IllegalTicketException{ + TicketEntity ticketEntity = mapper.mapToReservedEntity(ticket); + ticketRepository.save(ticketEntity); + + SeatEntity seatForTicket = ticketEntity.getSeat(); + reserveSeat(seatForTicket); + } + + @Transactional + public void reserveSeat(SeatEntity seat) throws IllegalSeatException { + if (seat != null) { + seat.reserve(); + } + } + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketType.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketType.java new file mode 100644 index 0000000..d4a129d --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/TicketType.java @@ -0,0 +1,18 @@ +package com.ramcel.cinema.reservation.functionalities.ticket; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.math.BigDecimal; + +@AllArgsConstructor +@Getter +public enum TicketType { + ADULT(BigDecimal.valueOf(25)), + STUDENT(BigDecimal.valueOf(18)), + CHILD(BigDecimal.valueOf(12.5)); + + private final BigDecimal price; + + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/validators/HolderValidator.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/validators/HolderValidator.java new file mode 100644 index 0000000..f2da6ac --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/validators/HolderValidator.java @@ -0,0 +1,55 @@ +package com.ramcel.cinema.reservation.functionalities.ticket.validators; + +import com.ramcel.cinema.reservation.functionalities.ticket.Ticket; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +public class HolderValidator { + private HolderValidator() { + } + + public static boolean validateHolder(Ticket ticket) { + return validateName(ticket.getName()) && validateSurname(ticket.getSurname()); + } + + private static boolean validateName(String name) { + return isChars(name) && checksNameConditions(name); + } + + private static boolean isChars(String name) { + return Arrays.stream(name.split("")) + .filter(s -> !s.isEmpty()) + .map(s -> Character.isLetter(s.charAt(0))) + .reduce((s, v) -> s && v).orElse(false); + } + + private static boolean checksNameConditions(String name) { + return isValidUppercaseStart(name) && name.length() >= 3; + } + + private static boolean isValidUppercaseStart(String name) { + String[] stringArray = name.split(""); + String first = stringArray[0]; + return (!first.isEmpty() && first.equals(first.toUpperCase())); + } + + private static boolean validateSurname(String surname) { + return Optional.of(surname) + .filter(canBeDividedAtDash(surname)) + .map(s -> List.of( + surname.substring(0, surname.indexOf('-')), + surname.substring(surname.indexOf('-') + 1))) + .orElse(List.of(surname)) + .stream() + .map(HolderValidator::validateName) + .reduce((s, v) -> s && v) + .get(); + } + + private static Predicate canBeDividedAtDash(String surname) { + return s -> (surname.indexOf('-') > 0) && (surname.indexOf('-') < surname.length() - 1); + } +} \ No newline at end of file diff --git a/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/validators/TicketValidator.java b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/validators/TicketValidator.java new file mode 100644 index 0000000..face199 --- /dev/null +++ b/src/main/java/com/ramcel/cinema/reservation/functionalities/ticket/validators/TicketValidator.java @@ -0,0 +1,184 @@ +package com.ramcel.cinema.reservation.functionalities.ticket.validators; + +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalSeatException; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalTicketException; +import com.ramcel.cinema.reservation.functionalities.seat.SeatStatus; +import com.ramcel.cinema.reservation.functionalities.ticket.Ticket; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Component +public class TicketValidator { + + @Autowired + private SeatRepository seatRepository; + + public TicketValidator(SeatRepository repository){ + this.seatRepository = repository; + } + + public boolean isValid(Ticket ticket){ + return this.isValid(List.of(ticket)); + } + + + public boolean isValid(List ticketList) throws IllegalReservationException{ + if (hasMultipleScreenings(ticketList)) { + throw new IllegalReservationException("Multiple screenings chosen"); + } + else { + try{ + return performValidation(ticketList);} + catch (IllegalTicketException e){ + throw new IllegalReservationException(e.getMessage() + " - ticket is not valid."); + } + } + + } + + private boolean hasMultipleScreenings(List ticketList){ + return ticketList.stream().mapToLong(Ticket::getScreeningId).distinct().count() > 1; + } + + private boolean performValidation(List ticketList) throws IllegalTicketException{ + boolean hasBasicParamsValid = passedBasicValidation(ticketList); + validateRowPosition(ticketList); + + return hasBasicParamsValid; + + } + + private boolean passedBasicValidation(Ticket ticket){ + boolean hasASeat = (ticket.getSeatId() > 0); + boolean hasValidHolder = HolderValidator.validateHolder(ticket); + boolean isMoreThan15MinsAway = ticket.getScreeningDate().isAfter(LocalDateTime.now().plusMinutes(15)); + return hasASeat && hasValidHolder && isMoreThan15MinsAway; + } + + private Boolean passedBasicValidation(List ticketList) { + return ticketList.stream() + .map(this::passedBasicValidation) + .reduce((t, k) -> t && k) + .orElseThrow(IllegalTicketException::new); + } + + private void validateRowPosition(List ticketList) throws IllegalTicketException { + + try{ + boolean isValid = performRowValidation(ticketList); + if(!isValid){ + throw new IllegalTicketException("Booking tickets from this list would cause holes in the seat row - illegal!"); + } + } + catch (IllegalSeatException e){ + throw new IllegalTicketException(e.getMessage()); + } + } + + private boolean performRowValidation(List ticketList) throws IllegalSeatException{ + Map> seatsByRowMap = computeSeatsByRowMap(ticketList); + reserveSeatsFromList(ticketList, seatsByRowMap); + + return canBookSeatWithoutHoles(seatsByRowMap); + + } + + private Map> computeSeatsByRowMap(List ticketList) throws IllegalSeatException{ + Map> bookedSeatsByRowMap = new HashMap<>(); + for(Ticket ticket: ticketList){ + SeatEntity currentSeat = fetchSeatEntity(ticket); + checkEntityTicketConsistency(ticket, currentSeat); + + addRowToMap(ticket, currentSeat, bookedSeatsByRowMap); + } + return bookedSeatsByRowMap; + } + + private SeatEntity fetchSeatEntity(Ticket ticket) throws IllegalSeatException { + return seatRepository.findById(ticket.getSeatId()) + .orElseThrow(IllegalSeatException::new); + } + + private static void checkEntityTicketConsistency(Ticket ticket, SeatEntity currentSeat) throws IllegalSeatException{ + if(currentSeat.isOccupied() || haveNonMatchingScreenings(ticket, currentSeat)){ + throw new IllegalSeatException("Seat can't be reserved - ticket data not in line with db."); + } + } + + private static boolean haveNonMatchingScreenings(Ticket ticket, SeatEntity currentSeat) { + return currentSeat.getScreening().getId() != ticket.getScreeningId(); + } + + private void addRowToMap(Ticket ticket, SeatEntity currentSeat, Map> bookedSeatsByRowMap) { + Long rowId = currentSeat.getRoomRow().getId(); + Long currentScreening = ticket.getScreeningId(); + List seatsInRow = getSeatsInRow(rowId, currentScreening); + + bookedSeatsByRowMap.computeIfAbsent(rowId, v -> seatsInRow); + } + + private List getSeatsInRow(Long rowId, Long currentScreening) { + return new ArrayList<>(seatRepository.findSeatsByRoomRowIdAndScreeningID(rowId, currentScreening)); + } + + private static void reserveSeatsFromList(List ticketList, Map> seatsByRowMap) throws IllegalTicketException{ + Set seatsToBeBookedIds = ticketList.stream().map(Ticket::getSeatId).collect(Collectors.toSet()); + + for(List seatsInRow: seatsByRowMap.values()){ + seatsInRow.stream().filter(v -> seatsToBeBookedIds.contains(v.getId())).forEach(SeatEntity::reserve); + } + } + + + private static boolean canBookSeatWithoutHoles(Map> bookedSeatsByRowMap) { + return bookedSeatsByRowMap.values() + .stream() + .map(seats -> seats.stream() + .sorted(Comparator.comparingInt(SeatEntity::getSeatNumber)) + .collect(Collectors.toList())) + .anyMatch(TicketValidator::hasNoHoles); + } + + private static boolean hasNoHoles(List seats) { + if(seats.size() == 3){ + if(hasAHole(seats)){ + return false; + } + } + for (int i = 0; i < seats.size() - 2; i++) { + SeatEntity seat1 = seats.get(i); + SeatEntity seat2 = seats.get(i + 1); + SeatEntity seat3 = seats.get(i + 2); + + if (seat1.getStatus() != SeatStatus.AVAILABLE + && seat2.getStatus() == SeatStatus.AVAILABLE + && seat3.getStatus() != SeatStatus.AVAILABLE) { + return false; + } + } + return true; + } + + private static boolean hasAHole(List seats) { + return seats.get(0).getStatus() != SeatStatus.AVAILABLE && seats.get(1).getStatus() == SeatStatus.AVAILABLE && seats.get(2).getStatus() != SeatStatus.AVAILABLE; + } + + + + + + + + + + + + +} diff --git a/src/main/java/com/ramcel/cinema/reservation/screening/MovieEntity.java b/src/main/java/com/ramcel/cinema/reservation/screening/MovieEntity.java deleted file mode 100644 index a256e05..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/screening/MovieEntity.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ramcel.cinema.reservation.screening; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.Duration; -import java.time.LocalDateTime; - -@Entity -@Data -@NoArgsConstructor -public class MovieEntity { - - @Id@GeneratedValue(strategy = GenerationType.IDENTITY) - private long movieId; - - private String name; - - private Duration runTime; - - public MovieEntity(String name, Duration runTime){ - this.name = name; - this.runTime=runTime; - } - - public MovieEntity(String name, String runTime){ - this(name, Duration.parse(runTime)); - } -} diff --git a/src/main/java/com/ramcel/cinema/reservation/screening/RoomRowEntity.java b/src/main/java/com/ramcel/cinema/reservation/screening/RoomRowEntity.java deleted file mode 100644 index 3bdf682..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/screening/RoomRowEntity.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ramcel.cinema.reservation.screening; - -import jakarta.persistence.*; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Entity -@Data -@NoArgsConstructor -public class RoomRowEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private int seatsInRow; - - @ManyToOne - private RoomEntity cinemaRoom; - - public RoomRowEntity(int seatsInRow, RoomEntity cinemaRoom){ - this.cinemaRoom = cinemaRoom; - this.seatsInRow = seatsInRow; - } - - -} diff --git a/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningController.java b/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningController.java deleted file mode 100644 index 70a32c7..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningController.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.ramcel.cinema.reservation.screening; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.time.LocalDate; -import java.time.LocalTime; - -@RestController -@RequestMapping("/screening") -public class ScreeningController { - - @Autowired - private ScreeningService screeningService; - - @GetMapping(value = "/find-screenings/{date}/{timestamp}", - produces = MediaType.APPLICATION_JSON_VALUE) - public String findScreenings(@PathVariable("date") LocalDate date, @PathVariable("timestamp") LocalTime time){ - - } - - - -} diff --git a/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningEntity.java b/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningEntity.java deleted file mode 100644 index c60ed7b..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningEntity.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ramcel.cinema.reservation.screening; - -import jakarta.persistence.*; -import lombok.Data; - -import java.time.LocalDateTime; - -@Entity -@Data -public class ScreeningEntity { - - @Id - @GeneratedValue - private long id; - - @ManyToOne - @JoinColumn(name = "movie_id") - private MovieEntity movieEntity; - - @ManyToOne - @JoinColumn(name = "room_id") - private RoomEntity roomEntity; - - @Column(columnDefinition = "TIMESTAMP") - private LocalDateTime dateTime; -} diff --git a/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningService.java b/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningService.java deleted file mode 100644 index ccfd254..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/screening/ScreeningService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ramcel.cinema.reservation.screening; - -import org.springframework.stereotype.Service; - -@Service -public interface ScreeningService { - - - -} diff --git a/src/main/java/com/ramcel/cinema/reservation/seat/SeatEntity.java b/src/main/java/com/ramcel/cinema/reservation/seat/SeatEntity.java deleted file mode 100644 index d7830db..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/seat/SeatEntity.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ramcel.cinema.reservation.seat; - -import com.ramcel.cinema.reservation.screening.RoomEntity; -import com.ramcel.cinema.reservation.screening.RoomRowEntity; -import com.ramcel.cinema.reservation.screening.ScreeningEntity; -import jakarta.persistence.*; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Entity -@Data -@NoArgsConstructor -public class SeatEntity { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - @ManyToOne - @JoinColumn(name = "room_id") - private RoomEntity room; - - @ManyToOne - @JoinColumn(name = "room_row_id") - private RoomRowEntity roomRow; - - private int seatNumber; - - @ManyToOne - @JoinColumn(name = "screening_id") - private ScreeningEntity screening; - - private boolean isOccupied; - - static enum SeatStatus {AVAILABLE, RESERVED, BOUGHT}; - - private SeatStatus status; -} diff --git a/src/main/java/com/ramcel/cinema/reservation/ticket/TicketEntity.java b/src/main/java/com/ramcel/cinema/reservation/ticket/TicketEntity.java deleted file mode 100644 index c4f6577..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/ticket/TicketEntity.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.ramcel.cinema.reservation.ticket; - -import com.ramcel.cinema.reservation.screening.ScreeningEntity; -import com.ramcel.cinema.reservation.seat.SeatEntity; -import jakarta.persistence.*; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Entity -@Data -@NoArgsConstructor -public class TicketEntity { - - @Id@GeneratedValue(strategy = GenerationType.IDENTITY) - private long id; - - private String name; - - private String surname; - - @ManyToOne - @JoinColumn(name = "screening_id") - private ScreeningEntity screening; - - @ManyToOne - @JoinColumn(name = "screening_date_time") - private LocalDateTime screeningDate; - - @OneToOne - @JoinColumn(name = "seat_id") - private SeatEntity seat; - - private TicketType type; -} diff --git a/src/main/java/com/ramcel/cinema/reservation/ticket/TicketType.java b/src/main/java/com/ramcel/cinema/reservation/ticket/TicketType.java deleted file mode 100644 index 7f35647..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/ticket/TicketType.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ramcel.cinema.reservation.ticket; - -public enum TicketType { - ADULT, - STUDENT, - CHILD - - -} diff --git a/src/main/java/com/ramcel/cinema/reservation/ticket/TicketsController.java b/src/main/java/com/ramcel/cinema/reservation/ticket/TicketsController.java deleted file mode 100644 index a6a59a6..0000000 --- a/src/main/java/com/ramcel/cinema/reservation/ticket/TicketsController.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ramcel.cinema.reservation.ticket; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/tickets") -public class TicketsController { - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..372f669 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,16 @@ +server.error.include-message=always +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/cinema +spring.datasource.username=cinema-app +spring.datasource.password=EPqKjMPYhwJIeWKVRVQ7 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +hibernate.dialect=org.hibernate.dialect.MySQLDialect + +spring.jpa.properties.hibernate.jdbc.time_zone=CET +spring.jpa.hibernate.ddl-auto=update + +logging.path=logs +logging.file=spring.log + +server.tomcat.accesslog.enabled=true diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8d45279 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,27 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${LOG_PATH}/${LOG_FILE} + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + ${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.gz + + + + + + + + + diff --git a/src/main/resources/templates/welcome.html b/src/main/resources/templates/welcome.html deleted file mode 100644 index e1651eb..0000000 --- a/src/main/resources/templates/welcome.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - Welcome to shamer - - - -
-

Welcome to the ticket reservation system.

-

.

- -
- - - diff --git a/src/test/java/com/ramcel/cinema/reservation/ApplicationTests.java b/src/test/java/com/ramcel/cinema/reservation/ApplicationTests.java deleted file mode 100644 index 6157e72..0000000 --- a/src/test/java/com/ramcel/cinema/reservation/ApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ramcel.cinema.reservation; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/com/ramcel/cinema/reservation/TicketValidatorTest.java b/src/test/java/com/ramcel/cinema/reservation/TicketValidatorTest.java new file mode 100644 index 0000000..b60d37a --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/TicketValidatorTest.java @@ -0,0 +1,134 @@ +package com.ramcel.cinema.reservation; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.ticket.Ticket; +import com.ramcel.cinema.reservation.functionalities.ticket.TicketType; +import com.ramcel.cinema.reservation.functionalities.ticket.validators.TicketValidator; +import com.ramcel.cinema.reservation.objects.SeatEntityMockValid; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class TicketValidatorTest { + + private TicketValidator ticketValidator; + private SeatRepository seatRepository; + + @BeforeEach + public void setup() { + seatRepository = mock(SeatRepository.class); + ticketValidator = new TicketValidator(seatRepository); + + when(seatRepository.findById(anyLong())).thenReturn(Optional.of(new SeatEntityMockValid())); + when(seatRepository.findSeatsByRoomRowIdAndScreeningID(anyLong(), anyLong())).thenReturn(List.of(new SeatEntityMockValid())); + } + + @Test + public void isValidWithValidTicket() throws IllegalReservationException { + Ticket validTicket = buildValidTestTicket(); + + boolean isValid = ticketValidator.isValid(validTicket); + + Assertions.assertTrue(isValid); + } + + + + private static Ticket buildValidTestTicket() { + return Ticket.builder() + .name("John") + .surname("Doe") + .screeningId(1L) + .screeningDate(LocalDateTime.now().plusDays(2)) + .seatId(2L) + .type(TicketType.STUDENT) + .build(); + } + + + @Test + public void isValidWithInvalidTicketHolder() { + Ticket invalidTicket = TicketValidatorTest.buildInvalidTestTicketHolderInvalid(); + + boolean isValid = ticketValidator.isValid(invalidTicket); + + Assertions.assertFalse(isValid); + } + + private static Ticket buildInvalidTestTicketHolderInvalid(){ + return Ticket.builder() + .name("as") + .surname("as") + .screeningId(1L) + .screeningDate(LocalDateTime.now().plusDays(2)) + .seatId(2L) + .type(TicketType.STUDENT) + .build(); + } + + @Test + public void isValidWithInvalidScreeningDate() { + Ticket invalidTicket = TicketValidatorTest.buildInvalidTestTicketScreeningDateInvalid(); + + boolean isValid = ticketValidator.isValid(invalidTicket); + + Assertions.assertFalse(isValid); + } + + private static Ticket buildInvalidTestTicketScreeningDateInvalid(){ + return Ticket.builder() + .name("John") + .surname("Doe") + .screeningId(1L) + .screeningDate(LocalDateTime.now()) + .seatId(2L) + .type(TicketType.STUDENT) + .build(); + } + + @Test + public void isValidWithInvalidHolderSurname() { + Ticket invalidTicket = TicketValidatorTest.buildInvalidTestTicketHolderSurnameInvalid(); + + boolean isValid = ticketValidator.isValid(invalidTicket); + + Assertions.assertFalse(isValid); + } + + private static Ticket buildInvalidTestTicketHolderSurnameInvalid(){ + return Ticket.builder() + .name("John") + .surname("Bi-ni") + .screeningId(1L) + .screeningDate(LocalDateTime.now().plusDays(2)) + .seatId(2L) + .type(TicketType.STUDENT) + .build(); + } + + + @Test + public void isValidWithMultipleScreenings() { + // Arrange + Ticket validTicket1 = Ticket.builder() + .screeningId(1L) + .seatId(2L) + .build(); + Ticket validTicket2 = Ticket.builder() + .screeningId(2L) + .seatId(3L) + .build(); + List ticketList = Arrays.asList(validTicket1, validTicket2); + + assertThrows(IllegalReservationException.class, () -> ticketValidator.isValid(ticketList)); + } + +} diff --git a/src/test/java/com/ramcel/cinema/reservation/controller/ScreeningControllerTest.java b/src/test/java/com/ramcel/cinema/reservation/controller/ScreeningControllerTest.java new file mode 100644 index 0000000..a06bc1f --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/controller/ScreeningControllerTest.java @@ -0,0 +1,62 @@ +package com.ramcel.cinema.reservation.controller; + +import com.ramcel.cinema.reservation.functionalities.controllers.ScreeningController; +import com.ramcel.cinema.reservation.functionalities.screening.Movie; +import com.ramcel.cinema.reservation.functionalities.screening.Screening; +import com.ramcel.cinema.reservation.functionalities.screening.ScreeningService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@WebMvcTest(ScreeningController.class) +public class ScreeningControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ScreeningService screeningService; + + @Test + public void return204OnEmptyDate() throws Exception { + when(screeningService.findScreenings(LocalDateTime.of(2022,10,19,2,30))).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/screening/find-screenings/2022-10-19T02:30")) + .andExpect(status().isNoContent()); + } + + @Test + public void return200OnValidRequest() throws Exception { + LocalDateTime input = LocalDateTime.of(2022,10,19,2,30); + Screening testScreening = new Screening(1, + new Movie("bobo", 3600), + 1, + input + ); + + when(screeningService.findScreenings(input)) + .thenReturn(List.of(testScreening)); + + mockMvc.perform(get("/screening/find-screenings/2022-10-19T02:30")) + .andExpect(status().isOk()); + } + + @Test + public void handleInvalidInput() throws Exception{ + + mockMvc.perform(get("/screening/find-screenings/7")) + .andExpect(status().isBadRequest()); + + } +} diff --git a/src/test/java/com/ramcel/cinema/reservation/controller/SeatControllerTest.java b/src/test/java/com/ramcel/cinema/reservation/controller/SeatControllerTest.java new file mode 100644 index 0000000..f5beee0 --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/controller/SeatControllerTest.java @@ -0,0 +1,73 @@ +package com.ramcel.cinema.reservation.controller; + +import com.ramcel.cinema.reservation.functionalities.controllers.SeatController; +import com.ramcel.cinema.reservation.functionalities.screening.Movie; +import com.ramcel.cinema.reservation.functionalities.screening.Screening; +import com.ramcel.cinema.reservation.functionalities.screening.ScreeningService; +import com.ramcel.cinema.reservation.functionalities.seat.Seat; +import com.ramcel.cinema.reservation.functionalities.seat.SeatService; +import com.ramcel.cinema.reservation.functionalities.seat.SeatStatus; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(SeatController.class) +public class SeatControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SeatService seatService; + + @Test + public void return204OnFullScreening() throws Exception { + when(seatService.getAvailableSeats(1L)).thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/seat/get-seats/1")) + .andExpect(status().isNoContent()); + } + + @Test + public void return200OnValidRequest() throws Exception { + long input = 1L; + Seat testSeat = new Seat( + 1, + 1, + 1, + 1, + 1, + false, + SeatStatus.AVAILABLE + ); + + when(seatService.getAvailableSeats(input)) + .thenReturn(List.of(testSeat)); + + mockMvc.perform(get("/seat/get-seats/1")) + .andExpect(status().isOk()); + } + + @Test + public void handleInvalidInput() throws Exception{ + + mockMvc.perform(get("/seat/get-seats/absdf")) + .andExpect(status().isBadRequest()); + + } +} + + + + diff --git a/src/test/java/com/ramcel/cinema/reservation/controller/TicketControllerTest.java b/src/test/java/com/ramcel/cinema/reservation/controller/TicketControllerTest.java new file mode 100644 index 0000000..e21e07e --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/controller/TicketControllerTest.java @@ -0,0 +1,112 @@ +package com.ramcel.cinema.reservation.controller; +import com.ramcel.cinema.reservation.functionalities.ticket.TicketType; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.reservation.Reservation; +import com.ramcel.cinema.reservation.functionalities.ticket.Ticket; +import com.ramcel.cinema.reservation.functionalities.ticket.TicketService; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class TicketControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private TicketService ticketService; + + @Test + public void testValidBookTickets() throws Exception { + List ticketList = setUpTicketList(); + + when(ticketService.bookTicket(any(List.class))).thenReturn(createValidReservation()); + + String jsonRequest = objectMapper.writeValueAsString(ticketList); + + mockMvc.perform(post("/tickets/book-tickets/") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(jsonRequest)) + .andExpect(status().isOk()); + } + + private static List setUpTicketList() { + List ticketList = new ArrayList<>(); + + Ticket validTicket1 = getValidTicket1(); + ticketList.add(validTicket1); + + Ticket validTicket2 = getValidTicket2(); + ticketList.add(validTicket2); + return ticketList; + } + + private static Ticket getValidTicket2() { + return Ticket.builder() + .name("Jane") + .surname("Smith") + .screeningId(2) + .screeningDate(LocalDateTime.now()) + .seatId(102) + .type(TicketType.CHILD) + .build(); + } + + private static Ticket getValidTicket1() { + return Ticket.builder() + .name("John") + .surname("Doe") + .screeningId(1) + .screeningDate(LocalDateTime.now()) + .seatId(101) + .type(TicketType.ADULT) + .build(); + } + + @Test + public void testInvalidBookTickets() throws Exception { + List invalidTicketList = new ArrayList<>(); + + Ticket invalidTicket1 = Ticket.builder() + .name("John") + .surname("Doe") + .screeningId(1) + .seatId(101) // Omitting the screeningDate field + .type(TicketType.ADULT) + .build(); + invalidTicketList.add(invalidTicket1); + + when(ticketService.bookTicket(any(List.class))).thenThrow(IllegalReservationException.class); + + String jsonRequest = objectMapper.writeValueAsString(invalidTicketList); + + mockMvc.perform(post("/tickets/book-tickets/") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(jsonRequest)) + .andExpect(status().isBadRequest()); + } + + private Reservation createValidReservation() {return new Reservation(Collections.emptyList(), BigDecimal.ONE, LocalDateTime.now()); + } +} diff --git a/src/test/java/com/ramcel/cinema/reservation/objects/MovieEntityMock.java b/src/test/java/com/ramcel/cinema/reservation/objects/MovieEntityMock.java new file mode 100644 index 0000000..81db897 --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/objects/MovieEntityMock.java @@ -0,0 +1,11 @@ +package com.ramcel.cinema.reservation.objects; + +import com.ramcel.cinema.reservation.db.entity.MovieEntity; + +public class MovieEntityMock extends MovieEntity { + + public MovieEntityMock(){ + super("Star Wars", 3600); + } + +} diff --git a/src/test/java/com/ramcel/cinema/reservation/objects/RoomRowEntityMockValid.java b/src/test/java/com/ramcel/cinema/reservation/objects/RoomRowEntityMockValid.java new file mode 100644 index 0000000..1c26823 --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/objects/RoomRowEntityMockValid.java @@ -0,0 +1,12 @@ +package com.ramcel.cinema.reservation.objects; + +import com.ramcel.cinema.reservation.db.entity.RoomEntity; +import com.ramcel.cinema.reservation.db.entity.RoomRowEntity; + +public class RoomRowEntityMockValid extends RoomRowEntity { + + public RoomRowEntityMockValid(){ + super(1L,10, new RoomEntity()); + } + +} diff --git a/src/test/java/com/ramcel/cinema/reservation/objects/ScreeningEntityMockValid.java b/src/test/java/com/ramcel/cinema/reservation/objects/ScreeningEntityMockValid.java new file mode 100644 index 0000000..2f36d12 --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/objects/ScreeningEntityMockValid.java @@ -0,0 +1,18 @@ +package com.ramcel.cinema.reservation.objects; + +import com.ramcel.cinema.reservation.db.entity.RoomEntity; +import com.ramcel.cinema.reservation.db.entity.ScreeningEntity; + +import java.time.LocalDateTime; + +public class ScreeningEntityMockValid extends ScreeningEntity { + + public ScreeningEntityMockValid(){ + super( + new MovieEntityMock(), + new RoomEntity(), + LocalDateTime.now().plusDays(2) + ); + this.setId(1L); + } +} diff --git a/src/test/java/com/ramcel/cinema/reservation/objects/SeatEntityMockValid.java b/src/test/java/com/ramcel/cinema/reservation/objects/SeatEntityMockValid.java new file mode 100644 index 0000000..99dbe7d --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/objects/SeatEntityMockValid.java @@ -0,0 +1,36 @@ +package com.ramcel.cinema.reservation.objects; + +import com.ramcel.cinema.reservation.db.entity.RoomEntity; +import com.ramcel.cinema.reservation.db.entity.ScreeningEntity; +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.functionalities.seat.SeatStatus; + +public class SeatEntityMockValid extends SeatEntity { + public SeatEntityMockValid(){ + super(new RoomEntity(), + new RoomRowEntityMockValid(), + 1, + new ScreeningEntityMockValid(), + false, + SeatStatus.AVAILABLE + ); + this.setId(1L); + } + + public SeatEntityMockValid(Long id){ + this(); + this.setId(id); + } + + public static SeatEntityMockValid withScreeningAndRow(Long roomRowId, Long screeningId){ + SeatEntityMockValid mock = SeatEntityMockValid.withScreening(screeningId); + mock.getRoomRow().setId(roomRowId); + return mock; + } + public static SeatEntityMockValid withScreening(Long screeningId){ + SeatEntityMockValid mock = new SeatEntityMockValid(); + mock.getScreening().setId(screeningId); + + return mock; + } +} diff --git a/src/test/java/com/ramcel/cinema/reservation/service/integration/ScreeningServiceIntegrationTest.java b/src/test/java/com/ramcel/cinema/reservation/service/integration/ScreeningServiceIntegrationTest.java new file mode 100644 index 0000000..c4a4cb5 --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/service/integration/ScreeningServiceIntegrationTest.java @@ -0,0 +1,77 @@ +package com.ramcel.cinema.reservation.service.integration; + + +import com.ramcel.cinema.reservation.db.repositories.ScreeningRepository; +import com.ramcel.cinema.reservation.functionalities.screening.Movie; +import com.ramcel.cinema.reservation.functionalities.screening.Screening; +import com.ramcel.cinema.reservation.functionalities.screening.ScreeningServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest +@Slf4j +@Sql(scripts = {"/sql_scripts/import_movies.sql", "/sql_scripts/import_rooms.sql", "/sql_scripts/import_screenings.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(scripts = {"/delete_test_data.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) +public class ScreeningServiceIntegrationTest { + + + @Autowired + private ScreeningServiceImpl screeningService; + + @Autowired + private ScreeningRepository repository; + + @Test + public void findValidScreeningDBDate(){ + LocalDateTime input = LocalDateTime.of(2023, 11,2, 2, 0); + + List actual = screeningService.findScreenings(input); + + Screening expectedScreening1 = new Screening(1, + new Movie("Star Wars", 3600), + 1, + LocalDateTime.of(2023,11,2, 4,15)); + + Screening expectedScreening2 = new Screening(7, + new Movie("Captain Hook", 5400), + 3, + LocalDateTime.of(2023,11,2, 4,15)); + + assertEquals(List.of(expectedScreening2,expectedScreening1), actual); + + } + + @Test + public void findValidScreeningDBDateMovie(){ + LocalDateTime input = LocalDateTime.of(2023, 11,2, 2, 0); + Movie testMovie = new Movie("Star Wars", 3600); + + List actual = screeningService.findScreenings(testMovie, input); + + Screening expectedScreening1 = new Screening(1, + testMovie, + 1, + LocalDateTime.of(2023,11,2, 4,15)); + + assertEquals(List.of(expectedScreening1), actual); + + } + + @Test + public void findInvalidScreeningDB(){ + LocalDateTime input = LocalDateTime.of(2022, 10,10, 3, 0); + + List actual = screeningService.findScreenings(input); + + assertEquals(List.of(), actual); + } + +} diff --git a/src/test/java/com/ramcel/cinema/reservation/service/integration/TicketServiceIntegrationTest.java b/src/test/java/com/ramcel/cinema/reservation/service/integration/TicketServiceIntegrationTest.java new file mode 100644 index 0000000..549b7d4 --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/service/integration/TicketServiceIntegrationTest.java @@ -0,0 +1,138 @@ +package com.ramcel.cinema.reservation.service.integration; + +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import com.ramcel.cinema.reservation.db.repositories.TicketRepository; +import com.ramcel.cinema.reservation.functionalities.exception.IllegalReservationException; +import com.ramcel.cinema.reservation.functionalities.reservation.Reservation; +import com.ramcel.cinema.reservation.functionalities.ticket.*; +import com.ramcel.cinema.reservation.functionalities.ticket.validators.TicketValidator; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Slf4j +@Sql(scripts = {"/sql_scripts/import_movies.sql", "/sql_scripts/import_rooms.sql", "/sql_scripts/import_screenings.sql", "/sql_scripts/import_seats.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(scripts = {"/delete_test_data.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) +public class TicketServiceIntegrationTest { + + + @Autowired + private TicketRepository ticketRepository; + + @Autowired + private SeatRepository seatRepository; + + @Autowired + private TicketService ticketService; + + @Autowired + private TicketMapper mapper; + + @Autowired + private TicketValidator validator; + + + @Test + public void bookValidTicketDB(){ + Ticket input = buildValidTestTicket(); + + LocalDateTime expirationDate = LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES); + BigDecimal price = input.getType().getPrice(); + + Reservation expected = new Reservation(List.of(input), price, expirationDate); + Reservation actual = ticketService.bookTicket(input); + + assertEquals(expected, actual); + } + + private static Ticket buildValidTestTicket() { + return Ticket.builder() + .name("John") + .surname("Doe") + .screeningId(2L) + .screeningDate(LocalDateTime.of(2023,11,1,11,0,0)) + .seatId(7L) + .type(TicketType.STUDENT) + .build(); + } + + private static Ticket buildValidTestTicket2() { + return Ticket.builder() + .name("Papa") + .surname("Japa") + .screeningId(2L) + .screeningDate(LocalDateTime.of(2023,11,1,11,0,0)) + .seatId(9L) + .type(TicketType.ADULT) + .build(); + } + + + //book single to get a hole in rows in screening1 + private static Ticket buildValidTestTicket3() { + return Ticket.builder() + .name("Papa") + .surname("Japa") + .screeningId(1L) + .screeningDate(LocalDateTime.of(2023,11,2,4,15,0)) + .seatId(1L) + .type(TicketType.ADULT) + .build(); + } + + + private static Ticket buildInvalidTestTicketSeatTaken() { + return Ticket.builder() + .name("John") + .surname("Doe") + .screeningId(2L) + .screeningDate(LocalDateTime.of(2023,11,1,11,0,0)) + .seatId(8L) + .type(TicketType.STUDENT) + .build(); + } + + @Test + public void bookValidTicketList(){ + Ticket ticket1 = buildValidTestTicket(); + Ticket ticket2 = buildValidTestTicket2(); + List input = List.of(ticket1,ticket2); + + BigDecimal price = BigDecimal.valueOf(25+18); + LocalDateTime expirationDate = LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES); + + Reservation expected = new Reservation(input, price, expirationDate); + Reservation actual = ticketService.bookTicket(input); + + assertEquals(expected, actual); + } + + @Test + public void bookInvalidTicket(){ + Ticket input = buildInvalidTestTicketSeatTaken(); + + assertThrowsExactly(IllegalReservationException.class, ()-> ticketService.bookTicket(input)); + } + + @Test + public void bookInvalidTicketWithHoles(){ + Ticket ticket1 = buildValidTestTicket3(); + + List input = List.of(ticket1); + + assertThrows(IllegalReservationException.class, () -> ticketService.bookTicket(input)); + } + + + +} diff --git a/src/test/java/com/ramcel/cinema/reservation/service/unit/ScreeningServiceTest.java b/src/test/java/com/ramcel/cinema/reservation/service/unit/ScreeningServiceTest.java new file mode 100644 index 0000000..805286d --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/service/unit/ScreeningServiceTest.java @@ -0,0 +1,115 @@ +package com.ramcel.cinema.reservation.service.unit; + +import com.ramcel.cinema.reservation.db.entity.MovieEntity; +import com.ramcel.cinema.reservation.db.entity.RoomEntity; +import com.ramcel.cinema.reservation.db.entity.ScreeningEntity; +import com.ramcel.cinema.reservation.db.repositories.ScreeningRepository; +import com.ramcel.cinema.reservation.functionalities.screening.Movie; +import com.ramcel.cinema.reservation.functionalities.screening.Screening; +import com.ramcel.cinema.reservation.functionalities.screening.ScreeningService; +import com.ramcel.cinema.reservation.functionalities.screening.ScreeningServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +@SpringBootTest +public class ScreeningServiceTest{ + + @Autowired + private ScreeningService screeningService; + + @MockBean + private ScreeningRepository screeningRepository; + + private LocalDateTime startPeriod; + private LocalDateTime endPeriod; + private ScreeningEntity testEntity; + + + + @BeforeEach + public void setup(){ + startPeriod = LocalDateTime.of(2023,10,19,0,30); + endPeriod = LocalDateTime.of(2023,10,19,5,0); + + RoomEntity mockRoom = new RoomEntity(); + mockRoom.setId(1L); + mockRoom.setNumberOfRows(10); + + + testEntity = new ScreeningEntity( + new MovieEntity("Star Wars", 3600), + mockRoom, + endPeriod.minusMinutes(45) + ); + testEntity.setId(1L); + } + + @Test + public void findValidScreeningMock(){ + LocalDateTime input = LocalDateTime.of(2023, 10,19, 1, 0); + + when(screeningRepository.findScreeningsInPeriod(startPeriod,endPeriod)) + .thenReturn(List.of(testEntity)); + + List screeningList = screeningService.findScreenings(input); + + assertEquals(List.of(testEntity.mapToScreening()), screeningList); + verify(this.screeningRepository).findScreeningsInPeriod(startPeriod,endPeriod); + } + + @Test + public void findValidMovieScreeningsMock(){ + LocalDateTime input = LocalDateTime.of(2023, 10,19, 1, 0); + + when(screeningRepository.findScreeningsInPeriod(startPeriod,endPeriod)) + .thenReturn(List.of(testEntity)); + + Movie testMovie = new Movie("Star Wars", 3600); + + List screeningList = screeningService.findScreenings(testMovie, input); + + + assertEquals(List.of(testEntity.mapToScreening()), screeningList); + } + + @Test + public void findValidMovieScreeningsMockByMovie(){ + LocalDateTime input = LocalDateTime.of(2023, 10,19, 1, 0); + + Movie testMovie = new Movie("Star Wars", 3600); + + when(screeningRepository.findScreeningsInPeriod(startPeriod,endPeriod)) + .thenReturn(List.of(testEntity)); + + List screeningList = screeningService.findScreenings(testMovie, input); + + assertEquals(List.of(testEntity.mapToScreening()), screeningList); + verify(this.screeningRepository).findScreeningsInPeriod(startPeriod,endPeriod); + } + + @Test + public void findInvalidScreeningsMock(){ + + LocalDateTime input = LocalDateTime.of(2022, 10,19, 1, 0); + + when(screeningRepository.findScreeningsInPeriod(startPeriod.minusYears(1),endPeriod.minusYears(1))) + .thenReturn(List.of()); + + List screeningList = screeningService.findScreenings(input); + + assertEquals(List.of(), screeningList); + } + + +} diff --git a/src/test/java/com/ramcel/cinema/reservation/service/unit/SeatServiceTest.java b/src/test/java/com/ramcel/cinema/reservation/service/unit/SeatServiceTest.java new file mode 100644 index 0000000..87b1dde --- /dev/null +++ b/src/test/java/com/ramcel/cinema/reservation/service/unit/SeatServiceTest.java @@ -0,0 +1,65 @@ +package com.ramcel.cinema.reservation.service.unit; + +import com.ramcel.cinema.reservation.db.entity.SeatEntity; +import com.ramcel.cinema.reservation.db.repositories.SeatRepository; +import com.ramcel.cinema.reservation.functionalities.seat.Seat; +import com.ramcel.cinema.reservation.functionalities.seat.SeatService; +import com.ramcel.cinema.reservation.functionalities.seat.SeatStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest +public class SeatServiceTest { + + + @Autowired + private SeatService seatService; + + @MockBean + private SeatRepository seatRepository; + + @Mock + private SeatEntity testSeat; + + @BeforeEach + public void setup(){ + + testSeat.setId(1L); + testSeat.setStatus(SeatStatus.AVAILABLE); + testSeat.setSeatNumber(1); + + } + + @Test + public void findValidSeatMock(){ + when(seatRepository.findSeatByScreeningId(1L)) + .thenReturn(List.of(testSeat)); + + when(testSeat.mapToSeat()).thenReturn( + Seat.builder() + .seatId(1) + .roomId(2) + .roomRowId(3) + .seatNumber(4) + .screeningId(5) + .isOccupied(false) + .status(SeatStatus.AVAILABLE) + .build() + ); + + List screeningList = seatService.getAvailableSeats(1L); + + assertEquals(List.of(testSeat.mapToSeat()), screeningList); + } + +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..46b3832 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,19 @@ +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa +server.port=8088 + +hibernate.dialect=org.hibernate.dialect.H2Dialect + +spring.jpa.properties.hibernate.jdbc.time_zone=CET +spring.jpa.hibernate.ddl-auto=create-drop + +#For test population +spring.jpa.defer-datasource-initialization=true + +spring.h2.console.enabled=true +spring.h2.console.path=/h2console/ + +logging.level.com.ramcel.cinema.reservation=DEBUG + diff --git a/src/test/resources/delete_test_data.sql b/src/test/resources/delete_test_data.sql new file mode 100644 index 0000000..f017bf0 --- /dev/null +++ b/src/test/resources/delete_test_data.sql @@ -0,0 +1,11 @@ +SET REFERENTIAL_INTEGRITY FALSE; +TRUNCATE TABLE movies; +TRUNCATE TABLE rooms; +TRUNCATE TABLE room_rows; +TRUNCATE TABLE screenings; +TRUNCATE TABLE seats; +TRUNCATE TABLE tickets; +SET REFERENTIAL_INTEGRITY TRUE; + + + diff --git a/src/test/resources/import_movies.sql b/src/test/resources/import_movies.sql new file mode 100644 index 0000000..a627fb1 --- /dev/null +++ b/src/test/resources/import_movies.sql @@ -0,0 +1,3 @@ +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (1, 'Star Wars', 3600); +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (2, 'Captain Hook', 5400); +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (3, 'Mis', 7200); \ No newline at end of file diff --git a/src/test/resources/import_rooms.sql b/src/test/resources/import_rooms.sql new file mode 100644 index 0000000..a2a6802 --- /dev/null +++ b/src/test/resources/import_rooms.sql @@ -0,0 +1,6 @@ +INSERT INTO rooms (id, number_of_rows) VALUES +(1, 2), +(2, 2), +(3,2); + + diff --git a/src/test/resources/import_screenings.sql b/src/test/resources/import_screenings.sql new file mode 100644 index 0000000..dc186d6 --- /dev/null +++ b/src/test/resources/import_screenings.sql @@ -0,0 +1,9 @@ +INSERT INTO screenings (id, movie_id, room_id, screening_start_time) VALUES +(1, 1, 1, '2023-11-02 04:15:00'), +(2, 2, 2, '2023-11-01 11:00:00'), +(3, 2, 3, '2023-11-10 11:00:00'), +(4, 3, 1, '2023-10-28 06:15:00'), +(5, 1, 2, '2023-10-29 06:15:00'), +(6, 3, 3, '2023-10-10 06:15:00'), +(7, 2, 3, '2023-11-02 04:15:00'); + diff --git a/src/test/resources/import_seats.sql b/src/test/resources/import_seats.sql new file mode 100644 index 0000000..21f36a5 --- /dev/null +++ b/src/test/resources/import_seats.sql @@ -0,0 +1,57 @@ +-- Populate the room-rows table with test values and specific IDs +INSERT INTO room_rows (id, seats_in_row, cinema_room_id) VALUES +--room 1 +(1, 3, 1), +(2, 3, 1), + +--room 2 +(3,2,2), +(4,2,2), + +-- room 3 +(5, 1, 3), +(6, 2, 3); + + +-- Populate the seats table with test values and specific IDs +INSERT INTO seats (id, room_id, room_row_id, seat_number, screening_id, is_occupied, status) VALUES +--room 1 screening 1 +(1, 1, 1, 1, 1, false, 'AVAILABLE'), +(2, 1, 1, 2, 1, false, 'AVAILABLE'), +(3, 1, 1, 3, 1, true, 'BOUGHT'), +(4, 1, 2, 1, 1, false, 'AVAILABLE'), +(5, 1, 2, 2, 1, false, 'AVAILABLE'), +(6, 1, 2, 3, 1, true, 'BOUGHT'), + +--room 1 screening 4 +(14, 1, 1, 1, 4, false, 'AVAILABLE'), +(15, 1, 1, 2, 4, false, 'AVAILABLE'), +(16, 1, 1, 3, 4, true, 'BOUGHT'), +(17, 1, 2, 1, 4, false, 'AVAILABLE'), +(18, 1, 2, 2, 4, false, 'AVAILABLE'), +(19, 1, 2, 3, 4, true, 'BOUGHT'), + +--room 2 screening 2 +(7, 2, 3, 2, 2, false, 'AVAILABLE'), +(8, 2, 3, 2, 2, true, 'BOUGHT'), +(9, 2, 4, 1, 2, false, 'AVAILABLE'), +(10, 2, 4, 2, 2, false, 'AVAILABLE'), + +-- room 2 screening 5 +(20, 2, 3, 1, 5, false, 'AVAILABLE'), +(21, 2, 3, 2, 5, false, 'AVAILABLE'), +(22, 2, 4, 1, 5, true, 'BOUGHT'), +(23, 2, 4, 2, 5, false, 'AVAILABLE'), + +--room 3 screening 3 +(11, 3, 5, 1, 3, false, 'AVAILABLE'), +(12, 3, 6, 2, 3, true, 'BOUGHT'), +(13, 3, 6, 1, 3, false, 'AVAILABLE'), + +--room 3 screening 6 +(24, 3, 5, 1, 6, true, 'BOUGHT'), +(25, 3, 6, 2, 6, false, 'AVAILABLE'), +(26, 3, 6, 1, 6, false, 'AVAILABLE'); + + + diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..1908c2d --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,27 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${LOG_PATH}/${LOG_FILE} + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + ${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.gz + + + + + + + + + diff --git a/src/test/resources/valid_ticket.json b/src/test/resources/valid_ticket.json new file mode 100644 index 0000000..825ca7b --- /dev/null +++ b/src/test/resources/valid_ticket.json @@ -0,0 +1,8 @@ +[{ + "name":"John", + "surname":"Doe", + "screeningId":1, + "screeningDate":"2023-11-02 04:15:00", + "seatId":1, + "type":"ADULT" +}] diff --git a/target/classes/application.properties b/target/classes/application.properties new file mode 100644 index 0000000..372f669 --- /dev/null +++ b/target/classes/application.properties @@ -0,0 +1,16 @@ +server.error.include-message=always + +spring.datasource.url=jdbc:mysql://127.0.0.1:3306/cinema +spring.datasource.username=cinema-app +spring.datasource.password=EPqKjMPYhwJIeWKVRVQ7 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +hibernate.dialect=org.hibernate.dialect.MySQLDialect + +spring.jpa.properties.hibernate.jdbc.time_zone=CET +spring.jpa.hibernate.ddl-auto=update + +logging.path=logs +logging.file=spring.log + +server.tomcat.accesslog.enabled=true diff --git a/target/classes/com/ramcel/cinema/reservation/Application.class b/target/classes/com/ramcel/cinema/reservation/Application.class new file mode 100644 index 0000000..c309c68 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/Application.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/BaseEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/BaseEntity.class new file mode 100644 index 0000000..0dd7b05 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/BaseEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/MovieEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/MovieEntity.class new file mode 100644 index 0000000..6e61e02 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/MovieEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/RoomEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/RoomEntity.class new file mode 100644 index 0000000..8c8b464 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/RoomEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/RoomRowEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/RoomRowEntity.class new file mode 100644 index 0000000..502876e Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/RoomRowEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/ScreeningEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/ScreeningEntity.class new file mode 100644 index 0000000..5eaf059 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/ScreeningEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/SeatEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/SeatEntity.class new file mode 100644 index 0000000..d163a71 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/SeatEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/TicketEntity$TicketEntityBuilder.class b/target/classes/com/ramcel/cinema/reservation/db/entity/TicketEntity$TicketEntityBuilder.class new file mode 100644 index 0000000..f587c21 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/TicketEntity$TicketEntityBuilder.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/entity/TicketEntity.class b/target/classes/com/ramcel/cinema/reservation/db/entity/TicketEntity.class new file mode 100644 index 0000000..1d9a289 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/entity/TicketEntity.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/repositories/ScreeningRepository.class b/target/classes/com/ramcel/cinema/reservation/db/repositories/ScreeningRepository.class new file mode 100644 index 0000000..08b0818 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/repositories/ScreeningRepository.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/repositories/SeatRepository.class b/target/classes/com/ramcel/cinema/reservation/db/repositories/SeatRepository.class new file mode 100644 index 0000000..a9d6d55 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/repositories/SeatRepository.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/db/repositories/TicketRepository.class b/target/classes/com/ramcel/cinema/reservation/db/repositories/TicketRepository.class new file mode 100644 index 0000000..a31a3e1 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/db/repositories/TicketRepository.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/ScreeningController.class b/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/ScreeningController.class new file mode 100644 index 0000000..ecd0479 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/ScreeningController.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/SeatController.class b/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/SeatController.class new file mode 100644 index 0000000..fb84318 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/SeatController.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/TicketController.class b/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/TicketController.class new file mode 100644 index 0000000..29b08c1 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/controllers/TicketController.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalReservationException.class b/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalReservationException.class new file mode 100644 index 0000000..0b72d33 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalReservationException.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalSeatException.class b/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalSeatException.class new file mode 100644 index 0000000..403aeb7 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalSeatException.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalTicketException.class b/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalTicketException.class new file mode 100644 index 0000000..d3a2ccc Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/exception/IllegalTicketException.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/reservation/Reservation.class b/target/classes/com/ramcel/cinema/reservation/functionalities/reservation/Reservation.class new file mode 100644 index 0000000..2cfaad5 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/reservation/Reservation.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/reservation/ReservationServiceImpl.class b/target/classes/com/ramcel/cinema/reservation/functionalities/reservation/ReservationServiceImpl.class new file mode 100644 index 0000000..a65dc1c Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/reservation/ReservationServiceImpl.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Movie.class b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Movie.class new file mode 100644 index 0000000..ab494a1 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Movie.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Room.class b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Room.class new file mode 100644 index 0000000..55f4515 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Room.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/screening/RoomRow.class b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/RoomRow.class new file mode 100644 index 0000000..ac5bb0f Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/RoomRow.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Screening.class b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Screening.class new file mode 100644 index 0000000..fa74920 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/Screening.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/screening/ScreeningService.class b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/ScreeningService.class new file mode 100644 index 0000000..e3b3bc6 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/ScreeningService.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/screening/ScreeningServiceImpl.class b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/ScreeningServiceImpl.class new file mode 100644 index 0000000..e553aed Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/screening/ScreeningServiceImpl.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/seat/Seat$SeatBuilder.class b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/Seat$SeatBuilder.class new file mode 100644 index 0000000..35dd455 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/Seat$SeatBuilder.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/seat/Seat.class b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/Seat.class new file mode 100644 index 0000000..cc8dad8 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/Seat.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatService.class b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatService.class new file mode 100644 index 0000000..619d7b1 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatService.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatServiceImpl.class b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatServiceImpl.class new file mode 100644 index 0000000..9c5d339 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatServiceImpl.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatStatus.class b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatStatus.class new file mode 100644 index 0000000..ac3e1f3 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/seat/SeatStatus.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/Ticket$TicketBuilder.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/Ticket$TicketBuilder.class new file mode 100644 index 0000000..a27921d Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/Ticket$TicketBuilder.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/Ticket.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/Ticket.class new file mode 100644 index 0000000..f0224b4 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/Ticket.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketMapper.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketMapper.class new file mode 100644 index 0000000..7450464 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketMapper.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketService.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketService.class new file mode 100644 index 0000000..ace53e5 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketService.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketServiceImpl.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketServiceImpl.class new file mode 100644 index 0000000..ae2dffb Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketServiceImpl.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketType.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketType.class new file mode 100644 index 0000000..ecd9267 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/TicketType.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/validators/HolderValidator.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/validators/HolderValidator.class new file mode 100644 index 0000000..0ed92f1 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/validators/HolderValidator.class differ diff --git a/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/validators/TicketValidator.class b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/validators/TicketValidator.class new file mode 100644 index 0000000..3571c53 Binary files /dev/null and b/target/classes/com/ramcel/cinema/reservation/functionalities/ticket/validators/TicketValidator.class differ diff --git a/target/classes/logback-spring.xml b/target/classes/logback-spring.xml new file mode 100644 index 0000000..8d45279 --- /dev/null +++ b/target/classes/logback-spring.xml @@ -0,0 +1,27 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${LOG_PATH}/${LOG_FILE} + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + ${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.gz + + + + + + + + + diff --git a/target/test-classes/application.properties b/target/test-classes/application.properties new file mode 100644 index 0000000..46b3832 --- /dev/null +++ b/target/test-classes/application.properties @@ -0,0 +1,19 @@ +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa +server.port=8088 + +hibernate.dialect=org.hibernate.dialect.H2Dialect + +spring.jpa.properties.hibernate.jdbc.time_zone=CET +spring.jpa.hibernate.ddl-auto=create-drop + +#For test population +spring.jpa.defer-datasource-initialization=true + +spring.h2.console.enabled=true +spring.h2.console.path=/h2console/ + +logging.level.com.ramcel.cinema.reservation=DEBUG + diff --git a/target/test-classes/com/ramcel/cinema/reservation/TicketValidatorTest.class b/target/test-classes/com/ramcel/cinema/reservation/TicketValidatorTest.class new file mode 100644 index 0000000..3ae725a Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/TicketValidatorTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/controller/ScreeningControllerTest.class b/target/test-classes/com/ramcel/cinema/reservation/controller/ScreeningControllerTest.class new file mode 100644 index 0000000..d35b9dd Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/controller/ScreeningControllerTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/controller/SeatControllerTest.class b/target/test-classes/com/ramcel/cinema/reservation/controller/SeatControllerTest.class new file mode 100644 index 0000000..5ce4291 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/controller/SeatControllerTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/controller/TicketControllerTest.class b/target/test-classes/com/ramcel/cinema/reservation/controller/TicketControllerTest.class new file mode 100644 index 0000000..841ed8c Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/controller/TicketControllerTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/objects/MovieEntityMock.class b/target/test-classes/com/ramcel/cinema/reservation/objects/MovieEntityMock.class new file mode 100644 index 0000000..b04bac4 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/objects/MovieEntityMock.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/objects/RoomRowEntityMockValid.class b/target/test-classes/com/ramcel/cinema/reservation/objects/RoomRowEntityMockValid.class new file mode 100644 index 0000000..4c3e440 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/objects/RoomRowEntityMockValid.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/objects/ScreeningEntityMockValid.class b/target/test-classes/com/ramcel/cinema/reservation/objects/ScreeningEntityMockValid.class new file mode 100644 index 0000000..2499289 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/objects/ScreeningEntityMockValid.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/objects/SeatEntityMockValid.class b/target/test-classes/com/ramcel/cinema/reservation/objects/SeatEntityMockValid.class new file mode 100644 index 0000000..6e892b4 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/objects/SeatEntityMockValid.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/service/integration/ScreeningServiceIntegrationTest.class b/target/test-classes/com/ramcel/cinema/reservation/service/integration/ScreeningServiceIntegrationTest.class new file mode 100644 index 0000000..cca4a9d Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/service/integration/ScreeningServiceIntegrationTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/service/integration/TicketServiceIntegrationTest.class b/target/test-classes/com/ramcel/cinema/reservation/service/integration/TicketServiceIntegrationTest.class new file mode 100644 index 0000000..ec96d10 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/service/integration/TicketServiceIntegrationTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/service/unit/ScreeningServiceTest.class b/target/test-classes/com/ramcel/cinema/reservation/service/unit/ScreeningServiceTest.class new file mode 100644 index 0000000..045cfc9 Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/service/unit/ScreeningServiceTest.class differ diff --git a/target/test-classes/com/ramcel/cinema/reservation/service/unit/SeatServiceTest.class b/target/test-classes/com/ramcel/cinema/reservation/service/unit/SeatServiceTest.class new file mode 100644 index 0000000..cc965af Binary files /dev/null and b/target/test-classes/com/ramcel/cinema/reservation/service/unit/SeatServiceTest.class differ diff --git a/target/test-classes/delete_test_data.sql b/target/test-classes/delete_test_data.sql new file mode 100644 index 0000000..f017bf0 --- /dev/null +++ b/target/test-classes/delete_test_data.sql @@ -0,0 +1,11 @@ +SET REFERENTIAL_INTEGRITY FALSE; +TRUNCATE TABLE movies; +TRUNCATE TABLE rooms; +TRUNCATE TABLE room_rows; +TRUNCATE TABLE screenings; +TRUNCATE TABLE seats; +TRUNCATE TABLE tickets; +SET REFERENTIAL_INTEGRITY TRUE; + + + diff --git a/target/test-classes/import_movies.sql b/target/test-classes/import_movies.sql new file mode 100644 index 0000000..a627fb1 --- /dev/null +++ b/target/test-classes/import_movies.sql @@ -0,0 +1,3 @@ +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (1, 'Star Wars', 3600); +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (2, 'Captain Hook', 5400); +INSERT INTO movies (id, name, run_time_in_seconds) VALUES (3, 'Mis', 7200); \ No newline at end of file diff --git a/target/test-classes/import_rooms.sql b/target/test-classes/import_rooms.sql new file mode 100644 index 0000000..a2a6802 --- /dev/null +++ b/target/test-classes/import_rooms.sql @@ -0,0 +1,6 @@ +INSERT INTO rooms (id, number_of_rows) VALUES +(1, 2), +(2, 2), +(3,2); + + diff --git a/target/test-classes/import_screenings.sql b/target/test-classes/import_screenings.sql new file mode 100644 index 0000000..dc186d6 --- /dev/null +++ b/target/test-classes/import_screenings.sql @@ -0,0 +1,9 @@ +INSERT INTO screenings (id, movie_id, room_id, screening_start_time) VALUES +(1, 1, 1, '2023-11-02 04:15:00'), +(2, 2, 2, '2023-11-01 11:00:00'), +(3, 2, 3, '2023-11-10 11:00:00'), +(4, 3, 1, '2023-10-28 06:15:00'), +(5, 1, 2, '2023-10-29 06:15:00'), +(6, 3, 3, '2023-10-10 06:15:00'), +(7, 2, 3, '2023-11-02 04:15:00'); + diff --git a/target/test-classes/import_seats.sql b/target/test-classes/import_seats.sql new file mode 100644 index 0000000..21f36a5 --- /dev/null +++ b/target/test-classes/import_seats.sql @@ -0,0 +1,57 @@ +-- Populate the room-rows table with test values and specific IDs +INSERT INTO room_rows (id, seats_in_row, cinema_room_id) VALUES +--room 1 +(1, 3, 1), +(2, 3, 1), + +--room 2 +(3,2,2), +(4,2,2), + +-- room 3 +(5, 1, 3), +(6, 2, 3); + + +-- Populate the seats table with test values and specific IDs +INSERT INTO seats (id, room_id, room_row_id, seat_number, screening_id, is_occupied, status) VALUES +--room 1 screening 1 +(1, 1, 1, 1, 1, false, 'AVAILABLE'), +(2, 1, 1, 2, 1, false, 'AVAILABLE'), +(3, 1, 1, 3, 1, true, 'BOUGHT'), +(4, 1, 2, 1, 1, false, 'AVAILABLE'), +(5, 1, 2, 2, 1, false, 'AVAILABLE'), +(6, 1, 2, 3, 1, true, 'BOUGHT'), + +--room 1 screening 4 +(14, 1, 1, 1, 4, false, 'AVAILABLE'), +(15, 1, 1, 2, 4, false, 'AVAILABLE'), +(16, 1, 1, 3, 4, true, 'BOUGHT'), +(17, 1, 2, 1, 4, false, 'AVAILABLE'), +(18, 1, 2, 2, 4, false, 'AVAILABLE'), +(19, 1, 2, 3, 4, true, 'BOUGHT'), + +--room 2 screening 2 +(7, 2, 3, 2, 2, false, 'AVAILABLE'), +(8, 2, 3, 2, 2, true, 'BOUGHT'), +(9, 2, 4, 1, 2, false, 'AVAILABLE'), +(10, 2, 4, 2, 2, false, 'AVAILABLE'), + +-- room 2 screening 5 +(20, 2, 3, 1, 5, false, 'AVAILABLE'), +(21, 2, 3, 2, 5, false, 'AVAILABLE'), +(22, 2, 4, 1, 5, true, 'BOUGHT'), +(23, 2, 4, 2, 5, false, 'AVAILABLE'), + +--room 3 screening 3 +(11, 3, 5, 1, 3, false, 'AVAILABLE'), +(12, 3, 6, 2, 3, true, 'BOUGHT'), +(13, 3, 6, 1, 3, false, 'AVAILABLE'), + +--room 3 screening 6 +(24, 3, 5, 1, 6, true, 'BOUGHT'), +(25, 3, 6, 2, 6, false, 'AVAILABLE'), +(26, 3, 6, 1, 6, false, 'AVAILABLE'); + + + diff --git a/target/test-classes/logback-test.xml b/target/test-classes/logback-test.xml new file mode 100644 index 0000000..1908c2d --- /dev/null +++ b/target/test-classes/logback-test.xml @@ -0,0 +1,27 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + ${LOG_PATH}/${LOG_FILE} + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + ${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.gz + + + + + + + + + diff --git a/target/test-classes/valid_ticket.json b/target/test-classes/valid_ticket.json new file mode 100644 index 0000000..825ca7b --- /dev/null +++ b/target/test-classes/valid_ticket.json @@ -0,0 +1,8 @@ +[{ + "name":"John", + "surname":"Doe", + "screeningId":1, + "screeningDate":"2023-11-02 04:15:00", + "seatId":1, + "type":"ADULT" +}]