diff --git a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java index d75ce64..d8161a6 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/PlaceService.java @@ -9,12 +9,15 @@ import com.kernellabs.kernellabs.presentation.dto.response.PlaceDetailResponse; import com.kernellabs.kernellabs.presentation.dto.response.PlaceListResponse; import com.kernellabs.kernellabs.presentation.dto.response.TimeSlotResponse; +import com.kernellabs.kernellabs.presentation.dto.response.enums.SlotStatus; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +36,7 @@ public List getAllPlace() { .collect(Collectors.toList()); } - public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date) { + public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date, Long editingReservationId) { // 1. 장소 정보 조회 Place place = placeRepository.findById(placeId) .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); @@ -42,39 +45,68 @@ public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date) LocalTime openTime = place.getOpenTime(); LocalTime closeTime = place.getCloseTime(); - // 3. 해당 날짜에 이미 예약된 시간 목록 조회 - List reservations = reservationRepository.findByPlaceIdAndReservationDate(placeId, date); - Set reservedSlots = getReservedSlots(reservations); + // 3. 해당 날짜의 '모든' 예약을 일단 다 가져온다. + List allReservationsOnDate = reservationRepository.findByPlaceIdAndReservationDate(placeId, date); - // 4. 전체 시간 슬롯 생성 및 예약 가능 여부 판단 - List timeSlots = generateTimeSlots(openTime, closeTime, reservedSlots); + // 4. '나의 예약' 시간과 '다른 사람 예약' 시간을 분리하여 Set으로 만든다. + Set myReservedSlots = getSlotsForSpecificReservation(allReservationsOnDate, editingReservationId); + Set othersReservedSlots = getSlotsForOtherReservations(allReservationsOnDate, editingReservationId); - // 5. 최종 응답 DTO 생성 및 반환 + // 5. 3가지 상태를 포함한 전체 시간 슬롯 리스트를 생성한다. + List timeSlots = generateTimeSlotsWithStatus(openTime, closeTime, myReservedSlots, othersReservedSlots); + + // 6. 최종 응답 DTO를 만들어 반환한다. return PlaceDetailResponse.of(place, timeSlots); } - private List generateTimeSlots(LocalTime openTime, LocalTime closeTime, Set reservedSlots) { + private List generateTimeSlotsWithStatus(LocalTime openTime, LocalTime closeTime, Set mySlots, Set otherSlots) { List slots = new ArrayList<>(); LocalTime currentTime = openTime; while (!currentTime.isAfter(closeTime.minusHours(1))) { - boolean isAvailable = !reservedSlots.contains(currentTime); - slots.add(new TimeSlotResponse(currentTime.toString(), isAvailable)); + SlotStatus status; + if (mySlots.contains(currentTime)) { + status = SlotStatus.MY_RESERVATION; + } else if (otherSlots.contains(currentTime)) { + status = SlotStatus.UNAVAILABLE; + } else { + status = SlotStatus.AVAILABLE; + } + slots.add(new TimeSlotResponse(currentTime.toString(), status)); currentTime = currentTime.plusHours(1); } return slots; } - private Set getReservedSlots(List reservations) { + private Set getSlotsForSpecificReservation(List reservations, Long reservationId) { + if (reservationId == null) { + return Collections.emptySet(); + } return reservations.stream() - .flatMap(reservation -> { - List slots = new ArrayList<>(); - LocalTime current = reservation.getStartTime(); - while (current.isBefore(reservation.getEndTime())) { - slots.add(current); - current = current.plusHours(1); - } - return slots.stream(); - }) + .filter(r -> r.getId().equals(reservationId)) + .flatMap(this::expandReservationToSlots) .collect(Collectors.toSet()); } + private Set getSlotsForOtherReservations(List reservations, Long reservationId) { + // '나의 예약 ID'가 없는 경우(신규 예약 모드)에는 모든 예약이 '다른 사람 예약'이 된다. + if (reservationId == null) { + return reservations.stream() + .flatMap(this::expandReservationToSlots) + .collect(Collectors.toSet()); + } + // '나의 예약 ID'가 있는 경우(수정 모드)에는 해당 예약을 제외한다. + return reservations.stream() + .filter(r -> !r.getId().equals(reservationId)) + .flatMap(this::expandReservationToSlots) + .collect(Collectors.toSet()); + } + + private Stream expandReservationToSlots(Reservation reservation) { + List slots = new ArrayList<>(); + LocalTime current = reservation.getStartTime(); + while (current.isBefore(reservation.getEndTime())) { + slots.add(current); + current = current.plusHours(1); + } + return slots.stream(); + } } diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java index d4fad37..ab0728a 100644 --- a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -10,6 +10,7 @@ import com.kernellabs.kernellabs.presentation.dto.request.ReservationDeleteRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationVerityRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @@ -22,6 +23,7 @@ @Service @RequiredArgsConstructor public class ReservationService { + private final ReservationRepository reservationRepository; private final PlaceRepository placeRepository; private final ReservationValidator reservationValidator; @@ -40,12 +42,19 @@ public ReservationResponse createReservation(ReservationRequest request) { request.getReservationDate(), request.getTimeSlots()); // 4. 예약 저장 및 응답 반환 - reservationRepository.save(reservation); - return ReservationResponse.from(reservation); + reservationRepository.save(reservation); + return ReservationResponse.from(reservation); } - public ReservationResponse getReservation(Long reservationId) { + @Transactional + public ReservationResponse getReservation(Long reservationId, ReservationVerityRequest request) { + // 1. ID로 예약 찾기 Reservation reservation = findReservationById(reservationId); + + // 2. 비밀번호와 request password 비교 + validatePassword(request.getPassword(), reservation.getPassword()); + + // 3.DTO 변환 return ReservationResponse.from(reservation); } diff --git a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java index e800d5f..e602647 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java +++ b/src/main/java/com/kernellabs/kernellabs/global/config/WebConfig.java @@ -10,7 +10,7 @@ public class WebConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:5173", "https://kernel-labs-fee.vercel.app/", "https://uscode.porogramr.site/") - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") .allowedHeaders("*") .allowCredentials(true); } diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java index a1fba65..f74284d 100644 --- a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java @@ -29,4 +29,7 @@ boolean existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAft ); List findByPlaceIdAndReservationDate(Long placeId, LocalDate date); + + // 특정 장소와 날짜의 모든 예약 조회 (특정 예약을 제외) + List findByPlaceIdAndReservationDateAndIdNot(Long placeId, LocalDate date, Long reservationId); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java index b83475b..49ddc7d 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/PlaceController.java @@ -31,11 +31,12 @@ public ResponseEntity getAllPlaces() { @GetMapping("/{placeId}") public ResponseEntity> getPlace(@PathVariable Long placeId, - @RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate date) { + @RequestParam(required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate date, + @RequestParam(required = false) Long editingReservationId) { // 날짜 파라미터가 없으면 오늘 날짜 기본값 LocalDate targetDate = (date == null) ? LocalDate.now() : date; - PlaceDetailResponse response = placeService.getPlaceDetailWithDate(placeId, targetDate); + PlaceDetailResponse response = placeService.getPlaceDetailWithDate(placeId, targetDate, editingReservationId); return ResponseEntity.ok(ApiResponse.success(response)); } } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java index 51445d0..41e5740 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -5,6 +5,7 @@ import com.kernellabs.kernellabs.presentation.dto.request.ReservationDeleteRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationVerityRequest; import com.kernellabs.kernellabs.presentation.dto.response.ReservationResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -31,9 +32,10 @@ public ResponseEntity> createReservation(@Reque return ResponseEntity.ok(ApiResponse.success(response)); } - @GetMapping("/{reservationId}") - public ResponseEntity> getReservation(@PathVariable Long reservationId) { - ReservationResponse response = reservationService.getReservation(reservationId); + @PostMapping("/{reservationId}") + public ResponseEntity> getReservation(@PathVariable Long reservationId, + @Valid @RequestBody ReservationVerityRequest request) { + ReservationResponse response = reservationService.getReservation(reservationId, request); return ResponseEntity.ok(ApiResponse.success(response)); } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java new file mode 100644 index 0000000..122ea2f --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java @@ -0,0 +1,16 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationVerityRequest { + + @NotBlank(message = "비밀번호는 필수입니다.") + @Pattern(regexp = "\\d{4}", message = "비밀번호는 4자리 숫자여야 합니다.") + private String password; + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java index 764ac2f..c59c6f5 100644 --- a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/TimeSlotResponse.java @@ -1,5 +1,6 @@ package com.kernellabs.kernellabs.presentation.dto.response; +import com.kernellabs.kernellabs.presentation.dto.response.enums.SlotStatus; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,6 +9,6 @@ public class TimeSlotResponse { private String time; - private boolean isAvailable; + private SlotStatus status; } diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java new file mode 100644 index 0000000..6ed4116 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java @@ -0,0 +1,7 @@ +package com.kernellabs.kernellabs.presentation.dto.response.enums; + +public enum SlotStatus { + AVAILABLE, // 예약 가능 (아무도 예약 안 함) + UNAVAILABLE, // 예약 불가 (다른 사람이 예약함) + MY_RESERVATION // 나의 예약 (선택/해제 가능) +}