feat: public api 90min rule, security config, floorplan drag-and-drop edit mode
This commit is contained in:
31
src/main/java/com/caspos/config/SecurityConfig.java
Normal file
31
src/main/java/com/caspos/config/SecurityConfig.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.caspos.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Public reservation endpoints — no auth required
|
||||
.requestMatchers("/api/v1/public/**").permitAll()
|
||||
// Internal endpoints — authenticated (placeholder, extend with JWT later)
|
||||
.requestMatchers("/api/v1/**").authenticated()
|
||||
.anyRequest().denyAll()
|
||||
)
|
||||
// Disable default form login for REST API
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
.httpBasic(AbstractHttpConfigurer::disable);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.caspos.controller;
|
||||
|
||||
import com.caspos.dto.CreateReservationRequest;
|
||||
import com.caspos.dto.ReservationDto;
|
||||
import com.caspos.service.PublicReservationService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/public/reservations")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class PublicReservationController {
|
||||
|
||||
private final PublicReservationService publicReservationService;
|
||||
|
||||
public PublicReservationController(PublicReservationService publicReservationService) {
|
||||
this.publicReservationService = publicReservationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/public/reservations
|
||||
* No auth required. endTime is always set to startTime + 90 min server-side.
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<ReservationDto> createPublic(@RequestBody CreateReservationRequest request) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(publicReservationService.createPublic(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.caspos.service;
|
||||
|
||||
import com.caspos.domain.Guest;
|
||||
import com.caspos.domain.Reservation;
|
||||
import com.caspos.domain.RestaurantTable;
|
||||
import com.caspos.dto.CreateReservationRequest;
|
||||
import com.caspos.dto.ReservationDto;
|
||||
import com.caspos.repository.GuestRepository;
|
||||
import com.caspos.repository.ReservationRepository;
|
||||
import com.caspos.repository.TableRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Public reservation service.
|
||||
* Business rule: endTime is ALWAYS set server-side to startTime + 90 minutes.
|
||||
* Client-supplied endTime is silently ignored.
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class PublicReservationService {
|
||||
|
||||
private static final int RESERVATION_DURATION_MINUTES = 90;
|
||||
|
||||
private final ReservationRepository reservationRepository;
|
||||
private final GuestRepository guestRepository;
|
||||
private final TableRepository tableRepository;
|
||||
|
||||
public PublicReservationService(
|
||||
ReservationRepository reservationRepository,
|
||||
GuestRepository guestRepository,
|
||||
TableRepository tableRepository
|
||||
) {
|
||||
this.reservationRepository = reservationRepository;
|
||||
this.guestRepository = guestRepository;
|
||||
this.tableRepository = tableRepository;
|
||||
}
|
||||
|
||||
public ReservationDto createPublic(CreateReservationRequest req) {
|
||||
// ── 1. Create guest ──
|
||||
Guest guest = new Guest();
|
||||
guest.setFirstName(req.firstName());
|
||||
guest.setLastName(req.lastName());
|
||||
guest.setPhone(req.phone());
|
||||
guest.setEmail(req.email());
|
||||
guest = guestRepository.save(guest);
|
||||
|
||||
// ── 2. Resolve tables ──
|
||||
List<RestaurantTable> tables = req.tableIds() == null
|
||||
? List.of()
|
||||
: tableRepository.findAllById(req.tableIds());
|
||||
|
||||
// ── 3. Enforce 90-min rule — ignore client endTime ──
|
||||
OffsetDateTime startTime = req.startTime();
|
||||
OffsetDateTime endTime = startTime.plusMinutes(RESERVATION_DURATION_MINUTES);
|
||||
|
||||
// ── 4. Persist ──
|
||||
Reservation reservation = new Reservation();
|
||||
reservation.setGuest(guest);
|
||||
reservation.setTables(tables);
|
||||
reservation.setStartTime(startTime);
|
||||
reservation.setEndTime(endTime); // always 90 min from start
|
||||
reservation.setGuestCount(req.guestCount());
|
||||
reservation.setSpecialRequests(req.specialRequests());
|
||||
reservation.setStatus(Reservation.Status.PENDING); // Public → starts as PENDING
|
||||
|
||||
Reservation saved = reservationRepository.save(reservation);
|
||||
|
||||
// ── 5. Map to DTO ──
|
||||
var guestDto = new ReservationDto.GuestDto(
|
||||
guest.getId(), guest.getFirstName(), guest.getLastName(),
|
||||
guest.getPhone(), guest.getEmail()
|
||||
);
|
||||
return new ReservationDto(
|
||||
saved.getId(), guestDto,
|
||||
saved.getTables().stream().map(RestaurantTable::getId).toList(),
|
||||
saved.getStartTime(), saved.getEndTime(),
|
||||
saved.getGuestCount(), saved.getStatus().name(),
|
||||
saved.getSpecialRequests(), saved.getCreatedAt().toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user