Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ticket-booking code review #1

Open
wants to merge 43 commits into
base: review
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
725d5f6
Added basic screening domain logic and jpa support
Elmacioro Feb 24, 2023
6aa823d
Added entities mapping, modified DTOs, finished screening part of domain
Elmacioro Feb 25, 2023
35f8d74
Removed a few unnecessary lombok annotations
Elmacioro Feb 25, 2023
1397527
Added lombok to EntityToDomainMapper
Elmacioro Feb 25, 2023
b0513c9
Fixed typo
Elmacioro Feb 25, 2023
5217170
Added few domain unit tests
Elmacioro Feb 26, 2023
1230b38
Added exception handling
Elmacioro Feb 26, 2023
3e7931f
Added database integration tests
Elmacioro Feb 26, 2023
1428ad9
Tidied up sql init data, added bash build script, added bash use case…
Elmacioro Feb 26, 2023
1f69fb7
Titied up tests
Elmacioro Feb 26, 2023
fea484d
Changed seats to list in Screening
Elmacioro Feb 26, 2023
9d2e815
Added reservation functionality
Elmacioro Feb 27, 2023
61a73bb
Updated use case script
Elmacioro Feb 28, 2023
56b4831
Fixed regexes for names
Elmacioro Feb 28, 2023
850caac
Seats can be booked 15 min before screening check added
Elmacioro Feb 28, 2023
40be48e
Added booking check whether seat exists for given screening
Elmacioro Feb 28, 2023
0a1d75e
Added domain to entities and entities to domain mappers
Elmacioro Feb 28, 2023
9fa7966
Added ticketType validity check, some refactoring
Elmacioro Feb 28, 2023
b034c09
Refactored Movie screenings sorting
Elmacioro Feb 28, 2023
3d5dd15
Removed few tests
Elmacioro Feb 28, 2023
ffacc60
Added logging, refactored exceptions
Elmacioro Mar 1, 2023
f38fd0b
Added reservation tests
Elmacioro Mar 1, 2023
ab6b679
Added readme
Elmacioro Mar 1, 2023
0918df0
Updated readme for github
Elmacioro Mar 1, 2023
fc861b1
Updated readme
Elmacioro Mar 1, 2023
3c076c0
Moved private methods in test
Elmacioro Mar 2, 2023
2c9c3c7
Refactored ReservationManagement methods
Elmacioro Mar 2, 2023
9723963
Changed seatInRowNumber to seatNumber
Elmacioro Mar 4, 2023
5c9e668
Changed ticketTypeId from requests to ticketTypeName
Elmacioro Mar 4, 2023
0c7dfd1
Moved throwing NoSuchScreeningException to ScreeningDao
Elmacioro Mar 4, 2023
bb90c87
Refactored validation for booking seats
Elmacioro Mar 4, 2023
aaf2ea4
Changed naming from free seats to available seats
Elmacioro Mar 4, 2023
b92b053
Changed response so only available seats are returned
Elmacioro Mar 4, 2023
321b6ce
Added assumption that time interval can be at max 1 week
Elmacioro Mar 4, 2023
247faaa
Clean up ReservationManagement
Elmacioro Mar 4, 2023
181db35
Splitted mapping and sorting in ScreeningManagement
Elmacioro Mar 5, 2023
2317f98
Changed ids that user can see to UUIDs, refactored code
Elmacioro Mar 5, 2023
8383712
Added mappers tests
Elmacioro Mar 5, 2023
3c14dbf
Added few missing NonNull checks
Elmacioro Mar 5, 2023
5571b80
Added one additional assumption to readme
Elmacioro Mar 5, 2023
e41c034
Removed useless comment
Elmacioro Mar 5, 2023
0d688a4
Updated useCase and readme
Elmacioro Mar 5, 2023
3508cb6
Added empty line at the end of some files
Elmacioro Mar 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ticket-booking


### Additional assumptions
- Requested time interval for point 1. of the scenario can be at max 1 week.

Choose a reason for hiding this comment

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

Skąd takie założenie?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Żeby ograniczyć liczbę jednorazowo pobieranych rekordów. Bez tego użytkownik mógłby pobrać seanse od teraz na kilka miesięcy w przód. Ograniczenie wyszukiwania do jednego tygodnia może być w pewnym sensie paginacją. Dodatkowo uznałem, że użytkownik szukając seansu rzadko potrzebowały dłuższego przedziału.

Choose a reason for hiding this comment

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

Super, cieszę się, że to zauważyłeś!

- Search for movie screenings for point 1. of the scenario can apply only to future screenings.
- Each row has the same number of seats
- Reservation expiration date is set to 5 minutes after booking the seats
- All tickets for single reservation must have the same currency

---

### How to build and run

To build and run the app you can use provided bash script
```
./buildAndRun.sh
```
Or you can use mvnw directly
```
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
```

System uses in-memory database with initialized dummy data.

---
You can use useCase.sh bash script to execute required use case.
It performs 3 REST calls and prints responses to the console.
```
./useCase.sh
```

Example output:
```agsl
1. Fetch movie screenings that start between 15:20 and 18:00 on 05.04.2023:
[{"movieTitle":"Django","screeningTimes":[{"screeningId":"4941fe3f-611b-48a2-b31d-ed2fc551069f","start":"2023-04-05T16:30:00","end":"2023-04-05T18:00:00"}]},{"movieTitle":"Hateful 8","screeningTimes":[{"screeningId":"cd174523-4630-4f36-9611-5a78d663e15a","start":"2023-04-05T15:30:00","end":"2023-04-05T17:00:00"},{"screeningId":"fd612a91-db41-4bc3-a794-f8c1a3d8e1d2","start":"2023-04-05T17:30:00","end":"2023-04-05T19:00:00"}]},{"movieTitle":"Pulp Fiction","screeningTimes":[{"screeningId":"a80be8de-0c61-4e0a-a3e5-95b055845c18","start":"2023-04-05T16:00:00","end":"2023-04-05T17:30:00"}]}]

Choose a reason for hiding this comment

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

Fajnie byłoby to przepuścić przez jakiś formater (jq?).

Copy link
Owner Author

Choose a reason for hiding this comment

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

Rzeczywiście byłoby czytelniej, sorka :)


2. Fetch Django screening details with an id of 4941fe3f-611b-48a2-b31d-ed2fc551069f:
{"roomName":"Room C","rowsNumber":5,"seatsInRowNumber":5,"availableSeats":[{"rowNumber":1,"seatNumber":4},{"rowNumber":1,"seatNumber":5},{"rowNumber":2,"seatNumber":1},{"rowNumber":2,"seatNumber":2},{"rowNumber":2,"seatNumber":3},{"rowNumber":2,"seatNumber":4},{"rowNumber":2,"seatNumber":5},{"rowNumber":3,"seatNumber":1},{"rowNumber":3,"seatNumber":2},{"rowNumber":3,"seatNumber":3},{"rowNumber":3,"seatNumber":4},{"rowNumber":3,"seatNumber":5},{"rowNumber":4,"seatNumber":1},{"rowNumber":4,"seatNumber":2},{"rowNumber":4,"seatNumber":3},{"rowNumber":4,"seatNumber":4},{"rowNumber":4,"seatNumber":5},{"rowNumber":5,"seatNumber":1},{"rowNumber":5,"seatNumber":2},{"rowNumber":5,"seatNumber":3},{"rowNumber":5,"seatNumber":4},{"rowNumber":5,"seatNumber":5}]}

3. Book 3 seats for the screening. One adult, one child and one student:
{"reservationId":"9011fe1c-9e19-4dd8-a2e2-9f26277a83fd","priceDto":{"currency":"PLN","amount":55.50},"expirationTime":"2023-03-05T20:10:26.054379365"}
```
1 change: 1 addition & 0 deletions buildAndRun.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./mvnw clean spring-boot:run -Dspring-boot.run.profiles=dev
53 changes: 52 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@
<description>Ticket booking app</description>
<properties>
<java.version>17</java.version>
<mapstruct.version>1.5.3.Final</mapstruct.version>
<guava.version>31.1-jre</guava.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand All @@ -32,6 +42,27 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>RELEASE</version>

Choose a reason for hiding this comment

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

To jeszcze działa?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Ups, miało być bez wersji, powinna być z parenta. LATEST i RELEASE chyba nie działają dla wersji pluginów ale w dependency już tak.

<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>

<build>
Expand All @@ -48,6 +79,26 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.elmc.booking.adapters.configuration;

import com.elmc.booking.domain.ports.incoming.ReservationService;
import com.elmc.booking.domain.ports.incoming.ScreeningService;
import com.elmc.booking.domain.ports.outgoing.ReservationRepository;
import com.elmc.booking.domain.ports.outgoing.ScreeningRepository;
import com.elmc.booking.domain.ports.outgoing.TicketTypeRepository;
import com.elmc.booking.domain.reservation.ReservationManagement;
import com.elmc.booking.domain.screening.ScreeningManagement;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ServicesConfiguration {

@Bean

Choose a reason for hiding this comment

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

Czemu tworzymy tak, a nie przez adnotację nad serwisem?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Oczywiście można dodać adnotację na poziomie serwisu domenowego, ale chciałem, żeby domena nie zawierała springowych adnotacji.

public ScreeningService getScreeningService(ScreeningRepository screeningRepository) {
return new ScreeningManagement(screeningRepository);
}

@Bean
public ReservationService getReservationService(ReservationRepository reservationRepository,
TicketTypeRepository ticketTypeRepository,
ScreeningRepository screeningRepository) {
return new ReservationManagement(reservationRepository, ticketTypeRepository, screeningRepository);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.elmc.booking.adapters.incoming.rest;

import com.elmc.booking.domain.reservation.exceptions.*;
import com.elmc.booking.domain.screening.exceptions.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {

Choose a reason for hiding this comment

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

Fajnie, że dużo tych wyjątków i obsłużone z głową!

Copy link
Owner Author

Choose a reason for hiding this comment

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

Dzięki :)


@ExceptionHandler
public ResponseEntity<ErrorMessage> invalidScreeningTimeIntervalExceptionHandler(InvalidScreeningTimeIntervalException exception) {
log.error("Exception thrown InvalidScreeningTimeIntervalException: {}", exception.getMessage(), exception);

Choose a reason for hiding this comment

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

Kiedy dobrze jest logować na error a kiedy na warn?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Error do błędów, które uniemożliwiają poprawne funkcjonowanie aplikacji, natomiast warn gdy coś jest nie tak, ale aplikacja działa prawidłowo. Tutaj można by to pozmieniać na warny.

Choose a reason for hiding this comment

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

Dla mnie fajną regułą kciuka jest czy człowiek (administrator?) musi reagować - jak tak to error, jak nie to warn.

ErrorMessage errorMessage = new ErrorMessage("startTime has to be before endTime [startTime: %s], [endTime: %s]"
.formatted(exception.getStartTime(), exception.getStartTime()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> tooLongIntervalExceptionHandler(TooLongIntervalException exception) {
log.error("Exception thrown TooLongIntervalException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage("Time interval between two dates can be at max 1 week [startTime: %s], [endTime: %s]"
.formatted(exception.getStartTime(), exception.getStartTime()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> searchForPastScreeningsExceptionHandler(SearchForPastScreeningsException exception) {
log.error("Exception thrown SearchForPastScreeningsException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage("Screenings search can only apply to future screenings [startTime: %s], [endTime: %s]"
.formatted(exception.getStartTime(), exception.getStartTime()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> noSuchScreeningExceptionHandler(NoSuchScreeningException exception) {
log.error("Exception thrown NoSuchScreeningException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage("No screening was found for provided id: [screeningId: %s]"
.formatted(exception.getScreeningId()));
return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> seatAlreadyBookedExceptionHandler(SeatAlreadyBookedException exception) {
log.error("Exception thrown SeatAlreadyBookedException: Provided seats are already booked: [seatIds: {}]",
exception.getSeatsToBook(), exception);
ErrorMessage errorMessage = new ErrorMessage("Provided seats have been already booked by another client");
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> singleSeatLeftOutAfterBookingExceptionHandler(SingleSeatLeftOutAfterBookingException exception) {
log.error("Exception thrown SingleSeatLeftOutAfterBookingException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage(
"Cannot book provided seats as one seat would be left out between two booked seats");
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> invalidFirstnameExceptionHandler(InvalidFirstnameException exception) {
log.error("Exception thrown InvalidFirstnameException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage(
"Invalid firstname provided. Firstname must be at least 3 characters long [firstname: %s]"
.formatted(exception.getFirstname()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> invalidSurnameExceptionHandler(InvalidSurnameException exception) {
log.error("Exception thrown InvalidSurnameException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage(
"Invalid surname provided. Surname must be at least 3 characters long [firstname: %s]"
.formatted(exception.getSurname()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> noTicketsForReservationExceptionHandler(NoTicketsForReservationException exception) {
log.error("Exception thrown NoTicketsForReservationException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage("Reservation must apply to at least one seat");
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> sameSeatChosenMultipleTimesExceptionHandler(SameSeatChosenMultipleTimesException exception) {
log.error("Exception thrown SameSeatChosenMultipleTimesException: Same seat was chosen multiple times [seatIds: {}]",
exception.getSeatsToBook(), exception);
ErrorMessage errorMessage = new ErrorMessage("You cannot chose the same seat more than once during booking");
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> bookingToLateExceptionHandler(BookingToLateException exception) {
log.error("Exception thrown BookingToLateException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage("Seats can be booked at least %d minutes before screening starts"
.formatted(exception.getMinutesToBookBeforeScreening()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> seatsNotExistExceptionHandler(SeatsNotExistException exception) {
log.error("Exception thrown SeatsNotExistException: Some provided seats do not exist for screening {}",
exception.getSeatsToBook(), exception);
ErrorMessage errorMessage = new ErrorMessage("Provided seats do not exist for given screening");
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> invalidTicketTypesExceptionHandler(InvalidTicketTypesException exception) {
log.error("Exception thrown InvalidTicketTypesException: Some provided ticket types do not exist {}",
exception.getProvidedTicketTypeNames(), exception);
ErrorMessage errorMessage = new ErrorMessage("Provided ticket types do not exist in the system");
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

@ExceptionHandler
public ResponseEntity<ErrorMessage> differentCurrenciesExceptionHandler(DifferentCurrenciesException exception) {
log.error("Exception thrown DifferentCurrenciesException: {}", exception.getMessage(), exception);
ErrorMessage errorMessage = new ErrorMessage(
"Only one currency can be used for single reservation [number of provided currencies: %d]"
.formatted(exception.getNumberOfCurrencies()));
return new ResponseEntity<>(errorMessage, HttpStatus.UNPROCESSABLE_ENTITY);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.elmc.booking.adapters.incoming.rest;

import lombok.Value;

import java.time.LocalDateTime;

@Value

Choose a reason for hiding this comment

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

Skoro to Java 17 to czy nie fajniej byłoby użyć recordów?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Rekordy nie mogą mieć dodatkowych pól poza tymi z konstruktora. ErrorMessage był początkowo rekordem ale podawanie za każdym razem daty przy tworzeniu obiektu jakoś średnio mi wyglądało, więc zmieniłem to na klasę z ustawioną datą.

public class ErrorMessage {
LocalDateTime dateTime = LocalDateTime.now();
String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.elmc.booking.adapters.incoming.rest;

import com.elmc.booking.domain.ports.dto.outgoing.CreatedReservationDto;
import com.elmc.booking.adapters.incoming.rest.request.ReservationRequest;
import com.elmc.booking.domain.ports.incoming.ReservationService;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/api/reservation")
@RestController
@Slf4j
@AllArgsConstructor
public class ReservationController {

private ReservationService reservationService;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CreatedReservationDto bookSeats(@RequestBody @Valid ReservationRequest reservationRequest) {
log.debug("New reservation request received {}", reservationRequest);
return reservationService.bookSeats(reservationRequest.toDto());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.elmc.booking.adapters.incoming.rest;

import com.elmc.booking.domain.ports.dto.outgoing.MovieScreeningsDto;
import com.elmc.booking.domain.ports.dto.outgoing.ScreeningDetailsDto;
import com.elmc.booking.domain.ports.incoming.ScreeningService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

@RequestMapping("/api/screening")
@RestController
@Slf4j
@AllArgsConstructor
public class ScreeningController {

private final ScreeningService screeningService;

@GetMapping
@ResponseStatus(HttpStatus.OK)
public List<MovieScreeningsDto> searchForScreenings(@RequestParam("start")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime start,
@RequestParam("end")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime end) {
log.debug("Search for movie screenings received [startTime: {}], [endTime: {}]", start, end);
return screeningService.searchForMovieScreenings(start, end);
}

@GetMapping("/{screeningId}")
@ResponseStatus(HttpStatus.OK)
public ScreeningDetailsDto getScreeningDetails(@PathVariable UUID screeningId) {
log.debug("Request for screening details received [screeningId: {}]", screeningId);
return screeningService.getScreeningDetails(screeningId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.elmc.booking.adapters.incoming.rest.request;

import com.elmc.booking.domain.ports.dto.incoming.RequestedReservationDto;
import jakarta.validation.constraints.NotNull;

import java.util.List;
import java.util.UUID;

public record ReservationRequest(@NotNull UUID screeningId,
@NotNull String firstname,
@NotNull String surname,
@NotNull List<TicketData> tickets) {

public RequestedReservationDto toDto() {
return new RequestedReservationDto(screeningId,
firstname,
surname,
tickets.stream()
.map(TicketData::toDto)
.toList());
}
}
Loading