Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,7 +36,7 @@ public List<PlaceListResponse> 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));
Expand All @@ -42,39 +45,68 @@ public PlaceDetailResponse getPlaceDetailWithDate(Long placeId, LocalDate date)
LocalTime openTime = place.getOpenTime();
LocalTime closeTime = place.getCloseTime();

// 3. 해당 날짜에 이미 예약된 시간 목록 조회
List<Reservation> reservations = reservationRepository.findByPlaceIdAndReservationDate(placeId, date);
Set<LocalTime> reservedSlots = getReservedSlots(reservations);
// 3. 해당 날짜의 '모든' 예약을 일단 다 가져온다.
List<Reservation> allReservationsOnDate = reservationRepository.findByPlaceIdAndReservationDate(placeId, date);

// 4. 전체 시간 슬롯 생성 및 예약 가능 여부 판단
List<TimeSlotResponse> timeSlots = generateTimeSlots(openTime, closeTime, reservedSlots);
// 4. '나의 예약' 시간과 '다른 사람 예약' 시간을 분리하여 Set으로 만든다.
Set<LocalTime> myReservedSlots = getSlotsForSpecificReservation(allReservationsOnDate, editingReservationId);
Set<LocalTime> othersReservedSlots = getSlotsForOtherReservations(allReservationsOnDate, editingReservationId);

// 5. 최종 응답 DTO 생성 및 반환
// 5. 3가지 상태를 포함한 전체 시간 슬롯 리스트를 생성한다.
List<TimeSlotResponse> timeSlots = generateTimeSlotsWithStatus(openTime, closeTime, myReservedSlots, othersReservedSlots);

// 6. 최종 응답 DTO를 만들어 반환한다.
return PlaceDetailResponse.of(place, timeSlots);
}

private List<TimeSlotResponse> generateTimeSlots(LocalTime openTime, LocalTime closeTime, Set<LocalTime> reservedSlots) {
private List<TimeSlotResponse> generateTimeSlotsWithStatus(LocalTime openTime, LocalTime closeTime, Set<LocalTime> mySlots, Set<LocalTime> otherSlots) {
List<TimeSlotResponse> 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<LocalTime> getReservedSlots(List<Reservation> reservations) {
private Set<LocalTime> getSlotsForSpecificReservation(List<Reservation> reservations, Long reservationId) {
if (reservationId == null) {
return Collections.emptySet();
}
return reservations.stream()
.flatMap(reservation -> {
List<LocalTime> 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<LocalTime> getSlotsForOtherReservations(List<Reservation> 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<LocalTime> expandReservationToSlots(Reservation reservation) {
List<LocalTime> slots = new ArrayList<>();
LocalTime current = reservation.getStartTime();
while (current.isBefore(reservation.getEndTime())) {
slots.add(current);
current = current.plusHours(1);
}
return slots.stream();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,6 +23,7 @@
@Service
@RequiredArgsConstructor
public class ReservationService {

private final ReservationRepository reservationRepository;
private final PlaceRepository placeRepository;
private final ReservationValidator reservationValidator;
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ boolean existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAft
);

List<Reservation> findByPlaceIdAndReservationDate(Long placeId, LocalDate date);

// 특정 장소와 날짜의 모든 예약 조회 (특정 예약을 제외)
List<Reservation> findByPlaceIdAndReservationDateAndIdNot(Long placeId, LocalDate date, Long reservationId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ public ResponseEntity<?> getAllPlaces() {

@GetMapping("/{placeId}")
public ResponseEntity<ApiResponse<PlaceDetailResponse>> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,9 +32,10 @@ public ResponseEntity<ApiResponse<ReservationResponse>> createReservation(@Reque
return ResponseEntity.ok(ApiResponse.success(response));
}

@GetMapping("/{reservationId}")
public ResponseEntity<ApiResponse<ReservationResponse>> getReservation(@PathVariable Long reservationId) {
ReservationResponse response = reservationService.getReservation(reservationId);
@PostMapping("/{reservationId}")
public ResponseEntity<ApiResponse<ReservationResponse>> getReservation(@PathVariable Long reservationId,
@Valid @RequestBody ReservationVerityRequest request) {
ReservationResponse response = reservationService.getReservation(reservationId, request);
return ResponseEntity.ok(ApiResponse.success(response));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -8,6 +9,6 @@
public class TimeSlotResponse {

private String time;
private boolean isAvailable;
private SlotStatus status;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kernellabs.kernellabs.presentation.dto.response.enums;

public enum SlotStatus {
AVAILABLE, // 예약 가능 (아무도 예약 안 함)
UNAVAILABLE, // 예약 불가 (다른 사람이 예약함)
MY_RESERVATION // 나의 예약 (선택/해제 가능)
}