diff --git a/src/main/java/com/slotline/reservation/ReservationController.java b/src/main/java/com/slotline/reservation/ReservationController.java new file mode 100644 index 0000000..93da7e5 --- /dev/null +++ b/src/main/java/com/slotline/reservation/ReservationController.java @@ -0,0 +1,86 @@ +package com.slotline.reservation; + +import com.slotline.reservation.dto.RecurringReservationRequest; +import com.slotline.reservation.dto.RecurringReservationResponse; +import com.slotline.reservation.dto.ReservationRequest; +import com.slotline.reservation.dto.ReservationResponse; +import com.slotline.reservation.dto.WaitlistRequest; +import com.slotline.reservation.dto.WaitlistResponse; +import com.slotline.security.AuthenticatedUser; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/api") +@Tag(name = "Reservations") +public class ReservationController { + + private final ReservationService reservationService; + private final WaitlistService waitlistService; + + public ReservationController(ReservationService reservationService, WaitlistService waitlistService) { + this.reservationService = reservationService; + this.waitlistService = waitlistService; + } + + @PostMapping("/reservations") + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "Book a slot on a resource (409 on conflict)") + public ReservationResponse create(@AuthenticationPrincipal AuthenticatedUser actor, + @Valid @RequestBody ReservationRequest request) { + return reservationService.create(actor, request); + } + + @PostMapping("/reservations/recurring") + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "Book a recurring series expanded from an RRULE (all-or-nothing)") + public RecurringReservationResponse createSeries(@AuthenticationPrincipal AuthenticatedUser actor, + @Valid @RequestBody RecurringReservationRequest request) { + return reservationService.createSeries(actor, request); + } + + @GetMapping("/reservations/mine") + @Operation(summary = "List the caller's reservations") + public List mine(@AuthenticationPrincipal AuthenticatedUser actor) { + return reservationService.listForUser(actor.id()); + } + + @PostMapping("/reservations/{id}/cancel") + @Operation(summary = "Cancel a reservation (owner or ADMIN); promotes the waitlist") + public ReservationResponse cancel(@AuthenticationPrincipal AuthenticatedUser actor, @PathVariable Long id) { + return reservationService.cancel(actor, id); + } + + @PostMapping("/reservations/waitlist") + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "Join the waitlist for a slot") + public WaitlistResponse joinWaitlist(@AuthenticationPrincipal AuthenticatedUser actor, + @Valid @RequestBody WaitlistRequest request) { + return waitlistService.join(actor, request); + } + + @GetMapping("/reservations/waitlist/mine") + @Operation(summary = "List the caller's waitlist entries") + public List myWaitlist(@AuthenticationPrincipal AuthenticatedUser actor) { + return waitlistService.listForUser(actor.id()); + } + + @GetMapping("/resources/{resourceId}/reservations") + @Operation(summary = "List active reservations for a resource") + public List forResource(@AuthenticationPrincipal AuthenticatedUser actor, + @PathVariable Long resourceId) { + return reservationService.listActiveForResource(actor, resourceId); + } +} diff --git a/src/main/java/com/slotline/reservation/dto/ReservationRequest.java b/src/main/java/com/slotline/reservation/dto/ReservationRequest.java new file mode 100644 index 0000000..992763c --- /dev/null +++ b/src/main/java/com/slotline/reservation/dto/ReservationRequest.java @@ -0,0 +1,11 @@ +package com.slotline.reservation.dto; + +import jakarta.validation.constraints.NotNull; + +import java.time.Instant; + +public record ReservationRequest( + @NotNull Long resourceId, + @NotNull Instant startTime, + @NotNull Instant endTime) { +} diff --git a/src/main/java/com/slotline/reservation/dto/ReservationResponse.java b/src/main/java/com/slotline/reservation/dto/ReservationResponse.java new file mode 100644 index 0000000..f1ebc1c --- /dev/null +++ b/src/main/java/com/slotline/reservation/dto/ReservationResponse.java @@ -0,0 +1,27 @@ +package com.slotline.reservation.dto; + +import com.slotline.reservation.Reservation; +import com.slotline.reservation.ReservationStatus; + +import java.time.Instant; + +public record ReservationResponse( + Long id, + Long resourceId, + Long userId, + Instant startTime, + Instant endTime, + ReservationStatus status, + Instant createdAt) { + + public static ReservationResponse from(Reservation reservation) { + return new ReservationResponse( + reservation.getId(), + reservation.getResource().getId(), + reservation.getUserId(), + reservation.getStartTime(), + reservation.getEndTime(), + reservation.getStatus(), + reservation.getCreatedAt()); + } +}