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"
+}]