feat: add reservation REST endpoints

This commit is contained in:
dualfroz committed 2026-08-25 12:11:03 +02:00
1 parent 9167830e82
commit a33c1d6da4
3 files changed
+124

No files matched your search

@@ -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<ReservationResponse> 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<WaitlistResponse> myWaitlist(@AuthenticationPrincipal AuthenticatedUser actor) {
return waitlistService.listForUser(actor.id());
}
@GetMapping("/resources/{resourceId}/reservations")
@Operation(summary = "List active reservations for a resource")
public List<ReservationResponse> forResource(@AuthenticationPrincipal AuthenticatedUser actor,
@PathVariable Long resourceId) {
return reservationService.listActiveForResource(actor, resourceId);
}
}
@@ -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) {
}
@@ -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());
}
}