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

implement stories 1-6 #177

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions src/main/java/com/tenniscourts/guests/Guest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString
public class Guest extends BaseEntity<Long> {

@Column
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/com/tenniscourts/guests/GuestController.java
Original file line number Diff line number Diff line change
@@ -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<GuestDTO> findById(@PathVariable Long id) {
return ResponseEntity.ok(guestService.findById(id));
}

@GetMapping("/guest")
@ApiOperation(value = "Find guest by name")
public ResponseEntity<GuestDTO> findByName(@RequestParam(value = "name") String name) {
return ResponseEntity.ok(guestService.findByName(name));
}

@GetMapping
@ApiOperation(value = "Find all guests")
public ResponseEntity<List<GuestDTO>> findAll() {
return ResponseEntity.ok(guestService.findAll());
}

@PostMapping
@ApiOperation(value = "Create a new guest")
public ResponseEntity<GuestDTO> add(@RequestBody @Valid GuestDTO guestDTO) {
return ResponseEntity.ok(guestService.add(guestDTO));
}

@PutMapping
@ApiOperation(value = "Update a guest")
public ResponseEntity<GuestDTO> update(@RequestBody @Valid GuestDTO guestDTO) {
return ResponseEntity.ok(guestService.update(guestDTO));
}

@DeleteMapping("/{id}")
@ApiOperation(value = "Delete a guest")
public ResponseEntity<Void> delete(@PathVariable Long id) {
guestService.delete(id);
return ResponseEntity.ok().build();
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/tenniscourts/guests/GuestDTO.java
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 11 additions & 0 deletions src/main/java/com/tenniscourts/guests/GuestMapper.java
Original file line number Diff line number Diff line change
@@ -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);
}
12 changes: 12 additions & 0 deletions src/main/java/com/tenniscourts/guests/GuestRepository.java
Original file line number Diff line number Diff line change
@@ -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<Guest, Long> {

Optional<Guest> findByName(String name);
}
56 changes: 56 additions & 0 deletions src/main/java/com/tenniscourts/guests/GuestService.java
Original file line number Diff line number Diff line change
@@ -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<GuestDTO> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Void> bookReservation(CreateReservationRequestDTO createReservationRequestDTO) {
@PostMapping
@ApiOperation(value = "Book a reservation for a tennis court at a given date schedule")
public ResponseEntity<Void> bookReservation(@RequestBody @Valid CreateReservationRequestDTO createReservationRequestDTO) {
return ResponseEntity.created(locationByEntity(reservationService.bookReservation(createReservationRequestDTO).getId())).build();
}

public ResponseEntity<ReservationDTO> findReservation(Long reservationId) {
@GetMapping("/{reservationId}")
@ApiOperation(value = "Find a reservation by id")
public ResponseEntity<ReservationDTO> findReservation(@PathVariable Long reservationId) {
return ResponseEntity.ok(reservationService.findReservation(reservationId));
}

public ResponseEntity<ReservationDTO> cancelReservation(Long reservationId) {
@ApiOperation(value = "Cancel a reservation")
@PutMapping("/{reservationId}")
public ResponseEntity<ReservationDTO> cancelReservation(@PathVariable Long reservationId) {
return ResponseEntity.ok(reservationService.cancelReservation(reservationId));
}

public ResponseEntity<ReservationDTO> rescheduleReservation(Long reservationId, Long scheduleId) {
@ApiOperation(value = "Reschedule a reservation")
@PutMapping("/{reservationId}/schedules/{scheduleId}")
public ResponseEntity<ReservationDTO> rescheduleReservation(@PathVariable Long reservationId, @PathVariable Long scheduleId) {
return ResponseEntity.ok(reservationService.rescheduleReservation(reservationId, scheduleId));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tenniscourts.reservations;

import com.tenniscourts.guests.GuestDTO;
import com.tenniscourts.schedules.ScheduleDTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -23,6 +24,8 @@ public class ReservationDTO {

private ScheduleDTO schedule;

private GuestDTO guest;

private String reservationStatus;

private ReservationDTO previousReservation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public interface ReservationMapper {

Reservation map(ReservationDTO source);

@InheritInverseConfiguration
ReservationDTO map(Reservation source);

@Mapping(target = "guest.id", source = "guestId")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.");
Expand Down Expand Up @@ -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.");
}
}

Expand All @@ -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<Reservation> 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());
}
}
}
22 changes: 15 additions & 7 deletions src/main/java/com/tenniscourts/schedules/ScheduleController.java
Original file line number Diff line number Diff line change
@@ -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<Void> addScheduleTennisCourt(CreateScheduleRequestDTO createScheduleRequestDTO) {
@PostMapping
@ApiOperation(value = "Create a schedule for a given tennis court")
public ResponseEntity<Void> addScheduleTennisCourt(@RequestBody @Valid CreateScheduleRequestDTO createScheduleRequestDTO) {
return ResponseEntity.created(locationByEntity(scheduleService.addSchedule(createScheduleRequestDTO.getTennisCourtId(), createScheduleRequestDTO).getId())).build();
}

//TODO: implement rest and swagger
public ResponseEntity<List<ScheduleDTO>> findSchedulesByDates(LocalDate startDate,
LocalDate endDate) {
@GetMapping
@ApiOperation(value = "Find all the schedules between 2 dates")
public ResponseEntity<List<ScheduleDTO>> 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<ScheduleDTO> findByScheduleId(Long scheduleId) {
@GetMapping("/{scheduleId}")
@ApiOperation(value = "Find schedule by id")
public ResponseEntity<ScheduleDTO> findByScheduleId(@PathVariable Long scheduleId) {
return ResponseEntity.ok(scheduleService.findSchedule(scheduleId));
}
}
Loading