From 3bf712f73ed34c2d268bf7fca682cf0dc7644e99 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:31:28 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[feat]=20#32=20-=20WebConfig=EC=97=90=20PAT?= =?UTF-8?q?CH=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kernellabs/kernellabs/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } From 9dff47c8b7977f496f4b6400fb1d7094e6abcbd4 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:31:45 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[fix]=20#32=20-=20response=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(status=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/dto/response/TimeSlotResponse.java | 3 ++- .../presentation/dto/response/enums/SlotStatus.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/response/enums/SlotStatus.java 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 // 나의 예약 (선택/해제 가능) +} From b8ae8d1197a307477b121b27692249d341019af0 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:32:30 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[fix]=20#32=20-=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EB=8C=80=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(=EC=98=88=EC=95=BD=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=8B=9C=20=EC=A0=91=EA=B7=BC=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernellabs/application/PlaceService.java | 72 +++++++++++++------ .../repository/ReservationRepository.java | 3 + .../controller/PlaceController.java | 5 +- 3 files changed, 58 insertions(+), 22 deletions(-) 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/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)); } } From 28e67e41142b903c38fe6909f9ff501dd7663c49 Mon Sep 17 00:00:00 2001 From: seoyeon-jung Date: Sat, 28 Jun 2025 14:37:59 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[fix]=20#32=20-=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ReservationService.java | 15 ++++++++++++--- .../controller/ReservationController.java | 8 +++++--- .../dto/request/ReservationVerityRequest.java | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationVerityRequest.java 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/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; + +}