From 88be48579f563c2db30734a17965a7e21381ea04 Mon Sep 17 00:00:00 2001 From: Cosmin Alexandru Negru Date: Mon, 28 Feb 2022 12:09:48 +0200 Subject: [PATCH] implement stories 1-6 --- .../java/com/tenniscourts/guests/Guest.java | 2 - .../tenniscourts/guests/GuestController.java | 54 ++ .../com/tenniscourts/guests/GuestDTO.java | 18 + .../com/tenniscourts/guests/GuestMapper.java | 11 + .../tenniscourts/guests/GuestRepository.java | 12 + .../com/tenniscourts/guests/GuestService.java | 56 ++ .../reservations/ReservationController.java | 24 +- .../reservations/ReservationDTO.java | 3 + .../reservations/ReservationMapper.java | 1 - .../reservations/ReservationService.java | 63 +- .../schedules/ScheduleController.java | 22 +- .../schedules/ScheduleRepository.java | 3 + .../schedules/ScheduleService.java | 58 +- .../tenniscourts/TennisCourtController.java | 22 +- src/main/resources/data.sql | 53 +- .../common/BaseControllerTest.java | 30 + .../common/TennisCourtApplicationTest.java | 11 + .../guests/GuestControllerTest.java | 136 +++++ .../ReservationControllerTest.java | 106 ++++ .../schedules/SchedulesControllerTest.java | 49 ++ swagger.yaml | 563 ++++++++++++++++++ 21 files changed, 1262 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/tenniscourts/guests/GuestController.java create mode 100644 src/main/java/com/tenniscourts/guests/GuestDTO.java create mode 100644 src/main/java/com/tenniscourts/guests/GuestMapper.java create mode 100644 src/main/java/com/tenniscourts/guests/GuestRepository.java create mode 100644 src/main/java/com/tenniscourts/guests/GuestService.java create mode 100644 src/test/java/com/tenniscourts/common/BaseControllerTest.java create mode 100644 src/test/java/com/tenniscourts/common/TennisCourtApplicationTest.java create mode 100644 src/test/java/com/tenniscourts/guests/GuestControllerTest.java create mode 100644 src/test/java/com/tenniscourts/reservations/ReservationControllerTest.java create mode 100644 src/test/java/com/tenniscourts/schedules/SchedulesControllerTest.java create mode 100644 swagger.yaml diff --git a/src/main/java/com/tenniscourts/guests/Guest.java b/src/main/java/com/tenniscourts/guests/Guest.java index a60bbedb..4ba5b77f 100644 --- a/src/main/java/com/tenniscourts/guests/Guest.java +++ b/src/main/java/com/tenniscourts/guests/Guest.java @@ -21,8 +21,6 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@EqualsAndHashCode(callSuper = true) -@ToString public class Guest extends BaseEntity { @Column diff --git a/src/main/java/com/tenniscourts/guests/GuestController.java b/src/main/java/com/tenniscourts/guests/GuestController.java new file mode 100644 index 00000000..28eef193 --- /dev/null +++ b/src/main/java/com/tenniscourts/guests/GuestController.java @@ -0,0 +1,54 @@ +package com.tenniscourts.guests; + +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +@AllArgsConstructor +@RestController +@RequestMapping("/guests") +public class GuestController { + + private final GuestService guestService; + + @GetMapping("/{id}") + @ApiOperation(value = "Find guest by id") + public ResponseEntity findById(@PathVariable Long id) { + return ResponseEntity.ok(guestService.findById(id)); + } + + @GetMapping("/guest") + @ApiOperation(value = "Find guest by name") + public ResponseEntity findByName(@RequestParam(value = "name") String name) { + return ResponseEntity.ok(guestService.findByName(name)); + } + + @GetMapping + @ApiOperation(value = "Find all guests") + public ResponseEntity> findAll() { + return ResponseEntity.ok(guestService.findAll()); + } + + @PostMapping + @ApiOperation(value = "Create a new guest") + public ResponseEntity add(@RequestBody @Valid GuestDTO guestDTO) { + return ResponseEntity.ok(guestService.add(guestDTO)); + } + + @PutMapping + @ApiOperation(value = "Update a guest") + public ResponseEntity update(@RequestBody @Valid GuestDTO guestDTO) { + return ResponseEntity.ok(guestService.update(guestDTO)); + } + + @DeleteMapping("/{id}") + @ApiOperation(value = "Delete a guest") + public ResponseEntity delete(@PathVariable Long id) { + guestService.delete(id); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/tenniscourts/guests/GuestDTO.java b/src/main/java/com/tenniscourts/guests/GuestDTO.java new file mode 100644 index 00000000..fc6969e6 --- /dev/null +++ b/src/main/java/com/tenniscourts/guests/GuestDTO.java @@ -0,0 +1,18 @@ +package com.tenniscourts.guests; + +import lombok.*; + +import javax.validation.constraints.NotNull; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Builder +public class GuestDTO { + + private Long id; + + @NotNull + private String name; +} diff --git a/src/main/java/com/tenniscourts/guests/GuestMapper.java b/src/main/java/com/tenniscourts/guests/GuestMapper.java new file mode 100644 index 00000000..49b542c5 --- /dev/null +++ b/src/main/java/com/tenniscourts/guests/GuestMapper.java @@ -0,0 +1,11 @@ +package com.tenniscourts.guests; + +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface GuestMapper { + + Guest map(GuestDTO source); + + GuestDTO map(Guest source); +} diff --git a/src/main/java/com/tenniscourts/guests/GuestRepository.java b/src/main/java/com/tenniscourts/guests/GuestRepository.java new file mode 100644 index 00000000..68f96e8f --- /dev/null +++ b/src/main/java/com/tenniscourts/guests/GuestRepository.java @@ -0,0 +1,12 @@ +package com.tenniscourts.guests; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface GuestRepository extends JpaRepository { + + Optional findByName(String name); +} diff --git a/src/main/java/com/tenniscourts/guests/GuestService.java b/src/main/java/com/tenniscourts/guests/GuestService.java new file mode 100644 index 00000000..4a3971b4 --- /dev/null +++ b/src/main/java/com/tenniscourts/guests/GuestService.java @@ -0,0 +1,56 @@ +package com.tenniscourts.guests; + +import com.tenniscourts.exceptions.EntityNotFoundException; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class GuestService { + + private final GuestRepository guestRepository; + private final GuestMapper guestMapper; + + @Transactional(readOnly = true) + public GuestDTO findById(Long id) { + return guestRepository.findById(id).map(guestMapper::map).orElseThrow(() -> { + throw new EntityNotFoundException("Guest with id " + id + " not found."); + }); + } + + @Transactional(readOnly = true) + public GuestDTO findByName(String name) { + return guestRepository.findByName(name).map(guestMapper::map).orElseThrow(() -> { + throw new EntityNotFoundException("Guest with name " + name + " not found."); + }); + } + + @Transactional(readOnly = true) + public List findAll() { + return guestRepository.findAll() + .stream() + .map(guestMapper::map) + .collect(Collectors.toList()); + } + + @Transactional + public GuestDTO add(GuestDTO guestDTO) { + return guestMapper.map(guestRepository.save(guestMapper.map(guestDTO))); + } + + @Transactional + public GuestDTO update(GuestDTO guestDTO) { + findById(guestDTO.getId()); + return guestMapper.map(guestRepository.save(guestMapper.map(guestDTO))); + } + + @Transactional + public void delete(Long guestId) { + findById(guestId); + guestRepository.deleteById(guestId); + } +} diff --git a/src/main/java/com/tenniscourts/reservations/ReservationController.java b/src/main/java/com/tenniscourts/reservations/ReservationController.java index 2b1297b2..4f9d2342 100644 --- a/src/main/java/com/tenniscourts/reservations/ReservationController.java +++ b/src/main/java/com/tenniscourts/reservations/ReservationController.java @@ -1,27 +1,43 @@ package com.tenniscourts.reservations; import com.tenniscourts.config.BaseRestController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; @AllArgsConstructor +@RestController +@RequestMapping("/reservations") public class ReservationController extends BaseRestController { private final ReservationService reservationService; - public ResponseEntity bookReservation(CreateReservationRequestDTO createReservationRequestDTO) { + @PostMapping + @ApiOperation(value = "Book a reservation for a tennis court at a given date schedule") + public ResponseEntity bookReservation(@RequestBody @Valid CreateReservationRequestDTO createReservationRequestDTO) { return ResponseEntity.created(locationByEntity(reservationService.bookReservation(createReservationRequestDTO).getId())).build(); } - public ResponseEntity findReservation(Long reservationId) { + @GetMapping("/{reservationId}") + @ApiOperation(value = "Find a reservation by id") + public ResponseEntity findReservation(@PathVariable Long reservationId) { return ResponseEntity.ok(reservationService.findReservation(reservationId)); } - public ResponseEntity cancelReservation(Long reservationId) { + @ApiOperation(value = "Cancel a reservation") + @PutMapping("/{reservationId}") + public ResponseEntity cancelReservation(@PathVariable Long reservationId) { return ResponseEntity.ok(reservationService.cancelReservation(reservationId)); } - public ResponseEntity rescheduleReservation(Long reservationId, Long scheduleId) { + @ApiOperation(value = "Reschedule a reservation") + @PutMapping("/{reservationId}/schedules/{scheduleId}") + public ResponseEntity rescheduleReservation(@PathVariable Long reservationId, @PathVariable Long scheduleId) { return ResponseEntity.ok(reservationService.rescheduleReservation(reservationId, scheduleId)); } } diff --git a/src/main/java/com/tenniscourts/reservations/ReservationDTO.java b/src/main/java/com/tenniscourts/reservations/ReservationDTO.java index 077bb59a..39a75219 100644 --- a/src/main/java/com/tenniscourts/reservations/ReservationDTO.java +++ b/src/main/java/com/tenniscourts/reservations/ReservationDTO.java @@ -1,5 +1,6 @@ package com.tenniscourts.reservations; +import com.tenniscourts.guests.GuestDTO; import com.tenniscourts.schedules.ScheduleDTO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -23,6 +24,8 @@ public class ReservationDTO { private ScheduleDTO schedule; + private GuestDTO guest; + private String reservationStatus; private ReservationDTO previousReservation; diff --git a/src/main/java/com/tenniscourts/reservations/ReservationMapper.java b/src/main/java/com/tenniscourts/reservations/ReservationMapper.java index b648ccf4..68fcfa82 100644 --- a/src/main/java/com/tenniscourts/reservations/ReservationMapper.java +++ b/src/main/java/com/tenniscourts/reservations/ReservationMapper.java @@ -9,7 +9,6 @@ public interface ReservationMapper { Reservation map(ReservationDTO source); - @InheritInverseConfiguration ReservationDTO map(Reservation source); @Mapping(target = "guest.id", source = "guestId") diff --git a/src/main/java/com/tenniscourts/reservations/ReservationService.java b/src/main/java/com/tenniscourts/reservations/ReservationService.java index 14aa4436..248e049f 100644 --- a/src/main/java/com/tenniscourts/reservations/ReservationService.java +++ b/src/main/java/com/tenniscourts/reservations/ReservationService.java @@ -1,25 +1,52 @@ package com.tenniscourts.reservations; +import com.tenniscourts.exceptions.AlreadyExistsEntityException; import com.tenniscourts.exceptions.EntityNotFoundException; +import com.tenniscourts.guests.Guest; +import com.tenniscourts.guests.GuestMapper; +import com.tenniscourts.guests.GuestService; +import com.tenniscourts.schedules.Schedule; +import com.tenniscourts.schedules.ScheduleMapper; +import com.tenniscourts.schedules.ScheduleService; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.List; @Service @AllArgsConstructor public class ReservationService { private final ReservationRepository reservationRepository; - private final ReservationMapper reservationMapper; + private final GuestService guestService; + private final GuestMapper guestMapper; + private final ScheduleService scheduleService; + private final ScheduleMapper scheduleMapper; + @Transactional public ReservationDTO bookReservation(CreateReservationRequestDTO createReservationRequestDTO) { - throw new UnsupportedOperationException(); + Guest guest = guestMapper.map(guestService.findById(createReservationRequestDTO.getGuestId())); + Schedule schedule = scheduleMapper.map(scheduleService.findSchedule(createReservationRequestDTO.getScheduleId())); + + checkScheduleIsBeforeCurrentTime(schedule); + checkReservationAlreadyExists(schedule); + + Reservation reservation = Reservation.builder() + .guest(guest) + .schedule(schedule) + .reservationStatus(ReservationStatus.READY_TO_PLAY) + .value(BigDecimal.TEN) + .build(); + + return reservationMapper.map(reservationRepository.save(reservation)); } + @Transactional(readOnly = true) public ReservationDTO findReservation(Long reservationId) { return reservationRepository.findById(reservationId).map(reservationMapper::map).orElseThrow(() -> { throw new EntityNotFoundException("Reservation not found."); @@ -56,8 +83,12 @@ private void validateCancellation(Reservation reservation) { throw new IllegalArgumentException("Cannot cancel/reschedule because it's not in ready to play status."); } - if (reservation.getSchedule().getStartDateTime().isBefore(LocalDateTime.now())) { - throw new IllegalArgumentException("Can cancel/reschedule only future dates."); + checkScheduleIsBeforeCurrentTime(reservation.getSchedule()); + } + + private void checkScheduleIsBeforeCurrentTime(Schedule schedule) { + if (schedule.getStartDateTime().isBefore(LocalDateTime.now())) { + throw new IllegalArgumentException("Can cancel/reschedule/book only future dates."); } } @@ -71,23 +102,37 @@ public BigDecimal getRefundValue(Reservation reservation) { return BigDecimal.ZERO; } - /*TODO: This method actually not fully working, find a way to fix the issue when it's throwing the error: - "Cannot reschedule to the same slot.*/ + @Transactional public ReservationDTO rescheduleReservation(Long previousReservationId, Long scheduleId) { - Reservation previousReservation = cancel(previousReservationId); + Reservation previousReservation = reservationMapper.map(findReservation(previousReservationId)); if (scheduleId.equals(previousReservation.getSchedule().getId())) { throw new IllegalArgumentException("Cannot reschedule to the same slot."); } - previousReservation.setReservationStatus(ReservationStatus.RESCHEDULED); - reservationRepository.save(previousReservation); + reschedule(previousReservation); ReservationDTO newReservation = bookReservation(CreateReservationRequestDTO.builder() .guestId(previousReservation.getGuest().getId()) .scheduleId(scheduleId) .build()); newReservation.setPreviousReservation(reservationMapper.map(previousReservation)); + return newReservation; } + + private void reschedule(Reservation previousReservation) { + validateCancellation(previousReservation); + previousReservation.setReservationStatus(ReservationStatus.RESCHEDULED); + reservationRepository.save(previousReservation); + } + + private void checkReservationAlreadyExists(Schedule schedule) { + List reservations = reservationRepository.findBySchedule_Id(schedule.getId()); + boolean reservationAlreadyExists = reservations.stream() + .anyMatch(reservation -> ReservationStatus.READY_TO_PLAY == reservation.getReservationStatus()); + if (reservationAlreadyExists) { + throw new AlreadyExistsEntityException("Reservation already exists for schedule " + schedule.getStartDateTime() + " and tennis court " + schedule.getTennisCourt().getName()); + } + } } diff --git a/src/main/java/com/tenniscourts/schedules/ScheduleController.java b/src/main/java/com/tenniscourts/schedules/ScheduleController.java index 7613e89d..a2842c56 100644 --- a/src/main/java/com/tenniscourts/schedules/ScheduleController.java +++ b/src/main/java/com/tenniscourts/schedules/ScheduleController.java @@ -1,32 +1,40 @@ package com.tenniscourts.schedules; import com.tenniscourts.config.BaseRestController; +import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; @AllArgsConstructor +@RestController +@RequestMapping("/schedules") public class ScheduleController extends BaseRestController { private final ScheduleService scheduleService; - //TODO: implement rest and swagger - public ResponseEntity addScheduleTennisCourt(CreateScheduleRequestDTO createScheduleRequestDTO) { + @PostMapping + @ApiOperation(value = "Create a schedule for a given tennis court") + public ResponseEntity addScheduleTennisCourt(@RequestBody @Valid CreateScheduleRequestDTO createScheduleRequestDTO) { return ResponseEntity.created(locationByEntity(scheduleService.addSchedule(createScheduleRequestDTO.getTennisCourtId(), createScheduleRequestDTO).getId())).build(); } - //TODO: implement rest and swagger - public ResponseEntity> findSchedulesByDates(LocalDate startDate, - LocalDate endDate) { + @GetMapping + @ApiOperation(value = "Find all the schedules between 2 dates") + public ResponseEntity> findSchedulesByDates(@RequestParam(value = "startDate") LocalDate startDate, + @RequestParam(value = "endDate") LocalDate endDate) { return ResponseEntity.ok(scheduleService.findSchedulesByDates(LocalDateTime.of(startDate, LocalTime.of(0, 0)), LocalDateTime.of(endDate, LocalTime.of(23, 59)))); } - //TODO: implement rest and swagger - public ResponseEntity findByScheduleId(Long scheduleId) { + @GetMapping("/{scheduleId}") + @ApiOperation(value = "Find schedule by id") + public ResponseEntity findByScheduleId(@PathVariable Long scheduleId) { return ResponseEntity.ok(scheduleService.findSchedule(scheduleId)); } } diff --git a/src/main/java/com/tenniscourts/schedules/ScheduleRepository.java b/src/main/java/com/tenniscourts/schedules/ScheduleRepository.java index 12a44c6d..8ab6293c 100644 --- a/src/main/java/com/tenniscourts/schedules/ScheduleRepository.java +++ b/src/main/java/com/tenniscourts/schedules/ScheduleRepository.java @@ -2,9 +2,12 @@ import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; public interface ScheduleRepository extends JpaRepository { List findByTennisCourt_IdOrderByStartDateTime(Long id); + Optional findByTennisCourt_IdAndStartDateTime(Long id, LocalDateTime startDateTime); } \ No newline at end of file diff --git a/src/main/java/com/tenniscourts/schedules/ScheduleService.java b/src/main/java/com/tenniscourts/schedules/ScheduleService.java index 5d94ee1a..cf0268d3 100644 --- a/src/main/java/com/tenniscourts/schedules/ScheduleService.java +++ b/src/main/java/com/tenniscourts/schedules/ScheduleService.java @@ -1,7 +1,13 @@ package com.tenniscourts.schedules; +import com.tenniscourts.exceptions.AlreadyExistsEntityException; +import com.tenniscourts.exceptions.EntityNotFoundException; +import com.tenniscourts.tenniscourts.TennisCourtDTO; +import com.tenniscourts.tenniscourts.TennisCourtMapper; +import com.tenniscourts.tenniscourts.TennisCourtRepository; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @@ -11,12 +17,18 @@ public class ScheduleService { private final ScheduleRepository scheduleRepository; - private final ScheduleMapper scheduleMapper; + private final TennisCourtRepository tennisCourtRepository; + private final TennisCourtMapper tennisCourtMapper; + @Transactional public ScheduleDTO addSchedule(Long tennisCourtId, CreateScheduleRequestDTO createScheduleRequestDTO) { - //TODO: implement addSchedule - return null; + + checkInputParameters(createScheduleRequestDTO); + checkScheduleAlreadyExists(tennisCourtId, createScheduleRequestDTO); + + Schedule schedule = createNewSchedule(tennisCourtId, createScheduleRequestDTO); + return scheduleMapper.map(scheduleRepository.save(schedule)); } public List findSchedulesByDates(LocalDateTime startDate, LocalDateTime endDate) { @@ -24,12 +36,48 @@ public List findSchedulesByDates(LocalDateTime startDate, LocalDate return null; } + @Transactional(readOnly = true) public ScheduleDTO findSchedule(Long scheduleId) { - //TODO: implement - return null; + return scheduleRepository.findById(scheduleId).map(scheduleMapper::map).orElseThrow(() -> { + throw new EntityNotFoundException("Schedule with id " + scheduleId + " not found."); + }); } + @Transactional(readOnly = true) public List findSchedulesByTennisCourtId(Long tennisCourtId) { return scheduleMapper.map(scheduleRepository.findByTennisCourt_IdOrderByStartDateTime(tennisCourtId)); } + + private void checkInputParameters(CreateScheduleRequestDTO createScheduleRequestDTO) { + if (createScheduleRequestDTO.getStartDateTime().isBefore(LocalDateTime.now())) { + throw new IllegalArgumentException("Cannot add schedules in the past."); + } + } + + private void checkScheduleAlreadyExists(Long tennisCourtId, CreateScheduleRequestDTO createScheduleRequestDTO) { + if (scheduleRepository.findByTennisCourt_IdAndStartDateTime( + tennisCourtId, createScheduleRequestDTO.getStartDateTime()).isPresent()) { + throw new AlreadyExistsEntityException("There is already a schedule slot created for the given start date and time."); + } + } + + private Schedule createNewSchedule(Long tennisCourtId, CreateScheduleRequestDTO createScheduleRequestDTO) { + TennisCourtDTO tennisCourtDTO = getTennisCourtDTO(tennisCourtId); + + LocalDateTime scheduleStart = createScheduleRequestDTO.getStartDateTime(); + LocalDateTime scheduleEnd = scheduleStart.plusHours(1L); + + return Schedule.builder() + .tennisCourt(tennisCourtMapper.map(tennisCourtDTO)) + .startDateTime(scheduleStart) + .endDateTime(scheduleEnd) + .build(); + } + + private TennisCourtDTO getTennisCourtDTO(Long tennisCourtId) { + return tennisCourtRepository.findById(tennisCourtId).map(tennisCourtMapper::map).orElseThrow(() -> { + throw new EntityNotFoundException("Tennis Court not found."); + }); + } + } diff --git a/src/main/java/com/tenniscourts/tenniscourts/TennisCourtController.java b/src/main/java/com/tenniscourts/tenniscourts/TennisCourtController.java index a014bdc6..24d1e540 100644 --- a/src/main/java/com/tenniscourts/tenniscourts/TennisCourtController.java +++ b/src/main/java/com/tenniscourts/tenniscourts/TennisCourtController.java @@ -1,26 +1,36 @@ package com.tenniscourts.tenniscourts; import com.tenniscourts.config.BaseRestController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; @AllArgsConstructor +@RestController +@RequestMapping("/tennis-courts") public class TennisCourtController extends BaseRestController { private final TennisCourtService tennisCourtService; - //TODO: implement rest and swagger - public ResponseEntity addTennisCourt(TennisCourtDTO tennisCourtDTO) { + @PostMapping + @ApiOperation(value = "Add new tennis court") + public ResponseEntity addTennisCourt(@RequestBody @Valid TennisCourtDTO tennisCourtDTO) { return ResponseEntity.created(locationByEntity(tennisCourtService.addTennisCourt(tennisCourtDTO).getId())).build(); } - //TODO: implement rest and swagger - public ResponseEntity findTennisCourtById(Long tennisCourtId) { + @GetMapping("/{tennisCourtId}") + @ApiOperation(value = "Get tennis court by id") + public ResponseEntity findTennisCourtById(@PathVariable Long tennisCourtId) { return ResponseEntity.ok(tennisCourtService.findTennisCourtById(tennisCourtId)); } - //TODO: implement rest and swagger - public ResponseEntity findTennisCourtWithSchedulesById(Long tennisCourtId) { + @GetMapping("/{tennisCourtId}/schedules") + @ApiOperation(value = "Get tennis courts with schedules") + public ResponseEntity findTennisCourtWithSchedulesById(@PathVariable Long tennisCourtId) { return ResponseEntity.ok(tennisCourtService.findTennisCourtWithSchedulesById(tennisCourtId)); } } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 191ed6af..92c5f470 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -8,4 +8,55 @@ insert schedule (id, start_date_time, end_date_time, tennis_court_id) values - (null, '2020-12-20T20:00:00.0', '2020-02-20T21:00:00.0', 1); \ No newline at end of file + (null, '2023-12-20T20:00:00.0', '2023-12-20T21:00:00.0', 1); + +insert + into + schedule + (id, start_date_time, end_date_time, tennis_court_id) + values + (null, '2024-12-20T20:00:00.0', '2024-12-20T21:00:00.0', 1); + +insert + into + schedule + (id, start_date_time, end_date_time, tennis_court_id) + values + (null, '2024-12-20T20:00:00.0', '2024-12-20T21:00:00.0', 1); + +insert + into + schedule + (id, start_date_time, end_date_time, tennis_court_id) + values + (null, '2025-12-20T20:00:00.0', '2025-12-20T21:00:00.0', 1); + +insert + into + schedule + (id, start_date_time, end_date_time, tennis_court_id) + values + (null, '2026-12-20T20:00:00.0', '2026-12-20T21:00:00.0', 1); + +-- past schedule +insert + into + schedule + (id, start_date_time, end_date_time, tennis_court_id) + values + (null, '2020-12-20T20:00:00.0', '2020-12-20T21:00:00.0', 1); + +insert into reservation (id, value, reservation_status, refund_value, guest_id, schedule_id) + values (null, 10, 0, 10, 1, 2); + +insert into reservation (id, value, reservation_status, refund_value, guest_id, schedule_id) + values (null, 10, 0, 10, 1, 3); + +insert into reservation (id, value, reservation_status, refund_value, guest_id, schedule_id) + values (null, 10, 1, 10, 1, 2); + +insert into reservation (id, value, reservation_status, refund_value, guest_id, schedule_id) + values (null, 10, 0, 10, 1, 2); + +insert into reservation (id, value, reservation_status, refund_value, guest_id, schedule_id) + values (null, 10, 0, 10, 1, 2); \ No newline at end of file diff --git a/src/test/java/com/tenniscourts/common/BaseControllerTest.java b/src/test/java/com/tenniscourts/common/BaseControllerTest.java new file mode 100644 index 00000000..5f494484 --- /dev/null +++ b/src/test/java/com/tenniscourts/common/BaseControllerTest.java @@ -0,0 +1,30 @@ +package com.tenniscourts.common; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringRunner.class) +@SpringBootTest +public abstract class BaseControllerTest { + + public MockMvc mockMvc; + + @Autowired + private WebApplicationContext wac; + + @Autowired + public ObjectMapper objectMapper; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } +} diff --git a/src/test/java/com/tenniscourts/common/TennisCourtApplicationTest.java b/src/test/java/com/tenniscourts/common/TennisCourtApplicationTest.java new file mode 100644 index 00000000..9fe81f19 --- /dev/null +++ b/src/test/java/com/tenniscourts/common/TennisCourtApplicationTest.java @@ -0,0 +1,11 @@ +package com.tenniscourts.common; + +import org.junit.Test; + +public class TennisCourtApplicationTest extends BaseControllerTest { + + @Test + public void contextLoads() { + + } +} diff --git a/src/test/java/com/tenniscourts/guests/GuestControllerTest.java b/src/test/java/com/tenniscourts/guests/GuestControllerTest.java new file mode 100644 index 00000000..ba8cb26c --- /dev/null +++ b/src/test/java/com/tenniscourts/guests/GuestControllerTest.java @@ -0,0 +1,136 @@ +package com.tenniscourts.guests; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tenniscourts.common.BaseControllerTest; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class GuestControllerTest extends BaseControllerTest { + + public static final String ROGER_FEDERER = "Roger Federer"; + public static final String RAFAEL_NADAL = "Rafael Nadal"; + + @Test + public void should_return_all_guests() throws Exception { + MvcResult result = mockMvc.perform(get("/guests")) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + List guests = objectMapper.readValue(json, List.class); + + assertThat(guests.size()).isGreaterThanOrEqualTo(1); + } + + @Test + public void should_return_guest_by_id() throws Exception { + MvcResult result = mockMvc.perform(get("/guests/1")) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + GuestDTO guest = objectMapper.readValue(json, GuestDTO.class); + + assertThat(guest.getName()).isEqualTo(ROGER_FEDERER); + } + + @Test + public void should_return_404_if_guest_not_found() throws Exception { + mockMvc.perform(get("/guests/100")) + .andExpect(status().isNotFound()); + } + + @Test + public void should_return_guest_by_name() throws Exception { + MvcResult result = mockMvc.perform(get("/guests/guest").param("name", ROGER_FEDERER)) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + GuestDTO guest = objectMapper.readValue(json, GuestDTO.class); + + assertThat(guest.getName()).isEqualTo(ROGER_FEDERER); + } + + @Test + public void should_add_new_guest() throws Exception { + GuestDTO guestRequestDTO = new GuestDTO(); + guestRequestDTO.setName("Dummy Name"); + + MvcResult result = mockMvc.perform(post("/guests") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(guestRequestDTO))) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + GuestDTO actualGuestDTO = new ObjectMapper().readValue(json, GuestDTO.class); + + assertThat(actualGuestDTO.getName()).isEqualTo(guestRequestDTO.getName()); + } + + @Test + public void should_return_400_if_add_new_guest_without_name() throws Exception { + mockMvc.perform(post("/guests") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(new GuestDTO()))) + .andExpect(status().isBadRequest()); + } + + @Test + public void should_update_guest() throws Exception { + GuestDTO guestRequestDTO = new GuestDTO(); + guestRequestDTO.setId(2L); + guestRequestDTO.setName("Dummy Name"); + + MvcResult result = mockMvc.perform(put("/guests") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(guestRequestDTO))) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + GuestDTO actualGuestDTO = new ObjectMapper().readValue(json, GuestDTO.class); + + assertThat(actualGuestDTO.getName()).isEqualTo(guestRequestDTO.getName()); + } + + @Test + public void should_return_404_if_guest_to_be_updated_not_found() throws Exception { + GuestDTO guestRequestDTO = new GuestDTO(); + guestRequestDTO.setId(100L); + guestRequestDTO.setName("Dummy Name"); + + mockMvc.perform(put("/guests") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(guestRequestDTO))) + .andExpect(status().isNotFound()); + } + + @Test + public void should_delete_guest() throws Exception { + mockMvc.perform(delete("/guests/2") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + mockMvc.perform(get("/guests/2")) + .andExpect(status().isNotFound()); + + } + + @Test + public void should_return_404_if_guest_to_be_deleted_is_not_found() throws Exception { + mockMvc.perform(delete("/guests/100") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } +} diff --git a/src/test/java/com/tenniscourts/reservations/ReservationControllerTest.java b/src/test/java/com/tenniscourts/reservations/ReservationControllerTest.java new file mode 100644 index 00000000..ed57870d --- /dev/null +++ b/src/test/java/com/tenniscourts/reservations/ReservationControllerTest.java @@ -0,0 +1,106 @@ +package com.tenniscourts.reservations; + +import com.tenniscourts.common.BaseControllerTest; +import org.junit.Test; +import org.springframework.http.MediaType; + +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class ReservationControllerTest extends BaseControllerTest { + + @Test + public void should_book_new_reservation() throws Exception { + CreateReservationRequestDTO createReservationRequestDTO = CreateReservationRequestDTO.builder() + .guestId(1L) + .scheduleId(5L) + .build(); + + mockMvc.perform(post("/reservations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createReservationRequestDTO))) + .andExpect(status().isCreated()); + } + + @Test + public void should_return_404_when_guest_is_not_found() throws Exception { + CreateReservationRequestDTO createReservationRequestDTO = CreateReservationRequestDTO.builder() + .guestId(100L) + .scheduleId(1L) + .build(); + + mockMvc.perform(post("/reservations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createReservationRequestDTO))) + .andExpect(status().isNotFound()); + } + + @Test + public void should_return_404_when_schedule_is_not_found() throws Exception { + CreateReservationRequestDTO createReservationRequestDTO = CreateReservationRequestDTO.builder() + .guestId(1L) + .scheduleId(100L) + .build(); + + mockMvc.perform(post("/reservations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createReservationRequestDTO))) + .andExpect(status().isNotFound()); + } + + @Test + public void should_return_400_when_schedule_is_in_the_past() throws Exception { + CreateReservationRequestDTO createReservationRequestDTO = CreateReservationRequestDTO.builder() + .guestId(1L) + .scheduleId(6L) + .build(); + + mockMvc.perform(post("/reservations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createReservationRequestDTO))) + .andExpect(status().isBadRequest()); + } + + @Test + public void should_return_409_if_reservation_already_exists() throws Exception { + CreateReservationRequestDTO createReservationRequestDTO = CreateReservationRequestDTO.builder() + .guestId(1L) + .scheduleId(2L) + .build(); + + mockMvc.perform(post("/reservations") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createReservationRequestDTO))) + .andExpect(status().isConflict()); + } + + @Test + public void should_cancel_reservation() throws Exception { + mockMvc.perform(put("/reservations/4") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + public void should_return_404_when_cancel_reservation_does_not_exists() throws Exception { + mockMvc.perform(put("/reservations/100") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + public void should_return_400_when_cancel_reservation_in_wrong_status() throws Exception { + mockMvc.perform(put("/reservations/3") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + public void should_reschedule_reservation() throws Exception { + mockMvc.perform(put("/reservations/5/schedules/1") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + +} diff --git a/src/test/java/com/tenniscourts/schedules/SchedulesControllerTest.java b/src/test/java/com/tenniscourts/schedules/SchedulesControllerTest.java new file mode 100644 index 00000000..61eac030 --- /dev/null +++ b/src/test/java/com/tenniscourts/schedules/SchedulesControllerTest.java @@ -0,0 +1,49 @@ +package com.tenniscourts.schedules; + +import com.tenniscourts.common.BaseControllerTest; +import org.junit.Test; +import org.springframework.http.MediaType; + +import java.time.LocalDateTime; + +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class SchedulesControllerTest extends BaseControllerTest { + + @Test + public void should_add_new_schedule_for_tennis_court() throws Exception { + CreateScheduleRequestDTO createScheduleRequestDTO = new CreateScheduleRequestDTO(); + createScheduleRequestDTO.setStartDateTime(LocalDateTime.now().plusYears(1L)); + createScheduleRequestDTO.setTennisCourtId(1L); + + mockMvc.perform(post("/schedules") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createScheduleRequestDTO))) + .andExpect(status().isCreated()); + } + + @Test + public void should_return_400_if_schedule_in_the_past() throws Exception { + CreateScheduleRequestDTO createScheduleRequestDTO = new CreateScheduleRequestDTO(); + createScheduleRequestDTO.setStartDateTime(LocalDateTime.now().minusYears(1L)); + createScheduleRequestDTO.setTennisCourtId(1L); + + mockMvc.perform(post("/schedules") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createScheduleRequestDTO))) + .andExpect(status().isBadRequest()); + } + + @Test + public void should_return_409_if_another_schedule_exists_at_the_same_time() throws Exception { + CreateScheduleRequestDTO createScheduleRequestDTO = new CreateScheduleRequestDTO(); + createScheduleRequestDTO.setStartDateTime(LocalDateTime.parse("2023-12-20T20:00:00.0")); + createScheduleRequestDTO.setTennisCourtId(1L); + + mockMvc.perform(post("/schedules") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createScheduleRequestDTO))) + .andExpect(status().isConflict()); + } +} diff --git a/swagger.yaml b/swagger.yaml new file mode 100644 index 00000000..da0c79e2 --- /dev/null +++ b/swagger.yaml @@ -0,0 +1,563 @@ +swagger: '2.0' +info: + description: Api Documentation + version: '1.0' + title: Api Documentation + termsOfService: urn:tos + contact: {} + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0 +host: localhost:8080 +basePath: / +tags: + - name: guest-controller + description: Guest Controller + - name: reservation-controller + description: Reservation Controller + - name: schedule-controller + description: Schedule Controller + - name: tennis-court-controller + description: Tennis Court Controller +paths: + /guests: + get: + tags: + - guest-controller + summary: Find all guests + operationId: findAllUsingGET + produces: + - '*/*' + responses: + '200': + description: OK + schema: + type: array + items: + $ref: '#/definitions/GuestDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + post: + tags: + - guest-controller + summary: Create a new guest + operationId: addUsingPOST + consumes: + - application/json + produces: + - '*/*' + parameters: + - in: body + name: guestDTO + description: guestDTO + required: true + schema: + $ref: '#/definitions/GuestDTO' + responses: + '200': + description: OK + schema: + $ref: '#/definitions/GuestDTO' + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + put: + tags: + - guest-controller + summary: Update a guest + operationId: updateUsingPUT + consumes: + - application/json + produces: + - '*/*' + parameters: + - in: body + name: guestDTO + description: guestDTO + required: true + schema: + $ref: '#/definitions/GuestDTO' + responses: + '200': + description: OK + schema: + $ref: '#/definitions/GuestDTO' + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /guests/guest: + get: + tags: + - guest-controller + summary: Find guest by name + operationId: findByNameUsingGET + produces: + - '*/*' + parameters: + - name: name + in: query + description: name + required: true + type: string + responses: + '200': + description: OK + schema: + $ref: '#/definitions/GuestDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /guests/{id}: + get: + tags: + - guest-controller + summary: Find guest by id + operationId: findByIdUsingGET + produces: + - '*/*' + parameters: + - name: id + in: path + description: id + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/GuestDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + delete: + tags: + - guest-controller + summary: Delete a guest + operationId: deleteUsingDELETE + produces: + - '*/*' + parameters: + - name: id + in: path + description: id + required: true + type: integer + format: int64 + responses: + '200': + description: OK + '204': + description: No Content + '401': + description: Unauthorized + '403': + description: Forbidden + deprecated: false + /reservations: + post: + tags: + - reservation-controller + summary: Book a reservation for a tennis court at a given date schedule + operationId: bookReservationUsingPOST + consumes: + - application/json + produces: + - '*/*' + parameters: + - in: body + name: createReservationRequestDTO + description: createReservationRequestDTO + required: true + schema: + $ref: '#/definitions/CreateReservationRequestDTO' + responses: + '200': + description: OK + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /reservations/{reservationId}: + get: + tags: + - reservation-controller + summary: Find a reservation by id + operationId: findReservationUsingGET + produces: + - '*/*' + parameters: + - name: reservationId + in: path + description: reservationId + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/ReservationDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + put: + tags: + - reservation-controller + summary: Cancel a reservation + operationId: cancelReservationUsingPUT + consumes: + - application/json + produces: + - '*/*' + parameters: + - name: reservationId + in: path + description: reservationId + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/ReservationDTO' + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /reservations/{reservationId}/schedules/{scheduleId}: + put: + tags: + - reservation-controller + summary: Reschedule a reservation + operationId: rescheduleReservationUsingPUT + consumes: + - application/json + produces: + - '*/*' + parameters: + - name: reservationId + in: path + description: reservationId + required: true + type: integer + format: int64 + - name: scheduleId + in: path + description: scheduleId + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/ReservationDTO' + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /schedules: + get: + tags: + - schedule-controller + summary: Find all the schedules between 2 dates + operationId: findSchedulesByDatesUsingGET + produces: + - '*/*' + parameters: + - name: endDate + in: query + description: endDate + required: true + type: string + format: date + - name: startDate + in: query + description: startDate + required: true + type: string + format: date + responses: + '200': + description: OK + schema: + type: array + items: + $ref: '#/definitions/ScheduleDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + post: + tags: + - schedule-controller + summary: Create a schedule for a given tennis court + operationId: addScheduleTennisCourtUsingPOST + consumes: + - application/json + produces: + - '*/*' + parameters: + - in: body + name: createScheduleRequestDTO + description: createScheduleRequestDTO + required: true + schema: + $ref: '#/definitions/CreateScheduleRequestDTO' + responses: + '200': + description: OK + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /schedules/{scheduleId}: + get: + tags: + - schedule-controller + summary: Find schedule by id + operationId: findByScheduleIdUsingGET + produces: + - '*/*' + parameters: + - name: scheduleId + in: path + description: scheduleId + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/ScheduleDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /tennis-courts: + post: + tags: + - tennis-court-controller + summary: Add new tennis court + operationId: addTennisCourtUsingPOST + consumes: + - application/json + produces: + - '*/*' + parameters: + - in: body + name: tennisCourtDTO + description: tennisCourtDTO + required: true + schema: + $ref: '#/definitions/TennisCourtDTO' + responses: + '200': + description: OK + '201': + description: Created + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /tennis-courts/{tennisCourtId}: + get: + tags: + - tennis-court-controller + summary: Get tennis court by id + operationId: findTennisCourtByIdUsingGET + produces: + - '*/*' + parameters: + - name: tennisCourtId + in: path + description: tennisCourtId + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/TennisCourtDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false + /tennis-courts/{tennisCourtId}/schedules: + get: + tags: + - tennis-court-controller + summary: Get tennis courts with schedules + operationId: findTennisCourtWithSchedulesByIdUsingGET + produces: + - '*/*' + parameters: + - name: tennisCourtId + in: path + description: tennisCourtId + required: true + type: integer + format: int64 + responses: + '200': + description: OK + schema: + $ref: '#/definitions/TennisCourtDTO' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + deprecated: false +definitions: + CreateReservationRequestDTO: + type: object + properties: + guestId: + type: integer + format: int64 + scheduleId: + type: integer + format: int64 + title: CreateReservationRequestDTO + CreateScheduleRequestDTO: + type: object + properties: + startDateTime: + type: string + format: date-time + tennisCourtId: + type: integer + format: int64 + title: CreateScheduleRequestDTO + GuestDTO: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + title: GuestDTO + ReservationDTO: + type: object + properties: + guest: + $ref: '#/definitions/GuestDTO' + guestId: + type: integer + format: int64 + id: + type: integer + format: int64 + previousReservation: + $ref: '#/definitions/ReservationDTO' + refundValue: + type: number + reservationStatus: + type: string + schedule: + $ref: '#/definitions/ScheduleDTO' + scheduledId: + type: integer + format: int64 + value: + type: number + title: ReservationDTO + ScheduleDTO: + type: object + properties: + endDateTime: + type: string + format: date-time + id: + type: integer + format: int64 + startDateTime: + type: string + format: date-time + tennisCourt: + $ref: '#/definitions/TennisCourtDTO' + tennisCourtId: + type: integer + format: int64 + title: ScheduleDTO + TennisCourtDTO: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + tennisCourtSchedules: + type: array + items: + $ref: '#/definitions/ScheduleDTO' + title: TennisCourtDTO