diff --git a/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java new file mode 100644 index 0000000..d4fad37 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/ReservationService.java @@ -0,0 +1,95 @@ +package com.kernellabs.kernellabs.application; + +import com.kernellabs.kernellabs.application.validator.ReservationValidator; +import com.kernellabs.kernellabs.domain.Place; +import com.kernellabs.kernellabs.domain.Reservation; +import com.kernellabs.kernellabs.global.exception.CustomException; +import com.kernellabs.kernellabs.global.exception.ErrorCode; +import com.kernellabs.kernellabs.infrastructure.repository.PlaceRepository; +import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; +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.response.ReservationResponse; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +@RequiredArgsConstructor +public class ReservationService { + private final ReservationRepository reservationRepository; + private final PlaceRepository placeRepository; + private final ReservationValidator reservationValidator; + + @Transactional + public ReservationResponse createReservation(ReservationRequest request) { + // 1. 장소 조회 + Place place = placeRepository.findById(request.getPlaceId()) + .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); + + // 2. 유효성 검증 + reservationValidator.validateForCreate(place, request); + + // 3. 엔티티 생성 + Reservation reservation = Reservation.create(place, request.getPassword(), + request.getReservationDate(), request.getTimeSlots()); + + // 4. 예약 저장 및 응답 반환 + reservationRepository.save(reservation); + return ReservationResponse.from(reservation); + } + + public ReservationResponse getReservation(Long reservationId) { + Reservation reservation = findReservationById(reservationId); + return ReservationResponse.from(reservation); + } + + @Transactional + public ReservationResponse updateReservation(Long reservationId, ReservationUpdateRequest request) { + // 1. 예약 조회 및 비밀번호 확인 + Reservation reservation = findReservationById(reservationId); + validatePassword(request.getPassword(), reservation.getPassword()); + + // 2. 변경 요청 유효성 검사 + reservationValidator.validateForUpdate(reservation, request); + + // 3. 엔티티 상태 변경 + reservation.updateTimes(request.getNewReservationDate(), parseStartTime(request.getNewTimeSlots()), parseEndTime(request.getNewTimeSlots())); + return ReservationResponse.from(reservation); + } + + @Transactional + public void deleteReservation(Long reservationId, ReservationDeleteRequest request) { + // 1. 예약 조회 및 비밀번호 확인 + Reservation reservation = findReservationById(reservationId); + validatePassword(request.getPassword(), reservation.getPassword()); + + // 2. 예약 삭제 + reservationRepository.delete(reservation); + } + + private LocalTime parseStartTime(List timeSlots) { + return LocalTime.parse(timeSlots.get(0), DateTimeFormatter.ofPattern("HH:mm")); + } + + private LocalTime parseEndTime(List timeSlots) { + return LocalTime.parse(timeSlots.get(timeSlots.size() - 1), DateTimeFormatter.ofPattern("HH:mm")).plusHours(1); + } + + private void validatePassword(String rawPassword, String storedPassword) { + if (!rawPassword.equals(storedPassword)) { + throw new CustomException(ErrorCode.INVALID_PASSWORD); + } + } + + private Reservation findReservationById(Long reservationId) { + return reservationRepository.findById(reservationId) + .orElseThrow(() -> new CustomException(ErrorCode.PLACE_NOT_FOUND)); + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java b/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java new file mode 100644 index 0000000..1cad215 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/application/validator/ReservationValidator.java @@ -0,0 +1,117 @@ +package com.kernellabs.kernellabs.application.validator; + +import com.kernellabs.kernellabs.domain.Place; +import com.kernellabs.kernellabs.domain.PlaceUnavailableDay; +import com.kernellabs.kernellabs.domain.Reservation; +import com.kernellabs.kernellabs.global.exception.CustomException; +import com.kernellabs.kernellabs.global.exception.ErrorCode; +import com.kernellabs.kernellabs.infrastructure.repository.PlaceUnavailableDayRepository; +import com.kernellabs.kernellabs.infrastructure.repository.ReservationRepository; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationRequest; +import com.kernellabs.kernellabs.presentation.dto.request.ReservationUpdateRequest; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ReservationValidator { + + private final ReservationRepository reservationRepository; + private final PlaceUnavailableDayRepository unavailableDayRepository; + private final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + + public void validateForCreate(Place place, ReservationRequest request) { + // 1. 시간 슬롯 자체의 유효성 검증 (포맷, 연속성) + validateTimeSlots(request.getTimeSlots()); + + LocalTime requestStartTime = LocalTime.parse(request.getTimeSlots().get(0), TIME_FORMATTER); + LocalTime requestEndTime = LocalTime.parse(request.getTimeSlots().get(request.getTimeSlots().size() - 1), TIME_FORMATTER).plusHours(1); + + // 2. 운영 시간 내의 요청인지 검증 + validateAgainstOperatingHours(place, request.getReservationDate(), requestStartTime, requestEndTime); + + // 3. 중복 예약이 없는지 검증 + validateNoOverlappingReservations(place.getId(), request.getReservationDate(), requestEndTime, requestStartTime); + } + + public void validateForUpdate(Reservation reservation, ReservationUpdateRequest request) { + LocalDate newDate = request.getNewReservationDate(); + List newTimeSlots = request.getNewTimeSlots(); + validateTimeSlots(newTimeSlots); + + LocalTime newStartTime = LocalTime.parse(request.getNewTimeSlots().get(0), TIME_FORMATTER); + LocalTime newEndTime = LocalTime.parse(request.getNewTimeSlots().get(request.getNewTimeSlots().size() - 1), TIME_FORMATTER).plusHours(1); + + validateAgainstOperatingHours(reservation.getPlace(), newDate, newStartTime, newEndTime); + // 자기 자신 제외하고 중복 검사 + validateNoOverlappingForUpdate(reservation, newDate, newEndTime, newStartTime); } + + // 시간 슬롯의 포맷과 연속성 검증 + private void validateTimeSlots(List timeSlots) { + if (timeSlots == null || timeSlots.isEmpty()) { + throw new CustomException(ErrorCode.TIME_SLOTS_EMPTY); + } + Collections.sort(timeSlots); + + for (int i = 0; i < timeSlots.size() - 1; i++) { + LocalTime current = LocalTime.parse(timeSlots.get(i), TIME_FORMATTER); + LocalTime next = LocalTime.parse(timeSlots.get(i + 1), TIME_FORMATTER); + if (!current.plusHours(1).equals(next)) { + throw new CustomException(ErrorCode.INVALID_TIME_SLOT_SEQUENCE); + } + } + } + + // 해당 날짜의 실제 운영 시간 기준으로 요청이 유효한지 검증 + private void validateAgainstOperatingHours(Place place, LocalDate date, LocalTime requestStartTime, LocalTime requestEndTime) { + OperatingHours operatingHours = getOperatingHoursFor(place, date); + + if (requestStartTime.isBefore(operatingHours.openTime()) || requestEndTime.isAfter(operatingHours.closeTime())) { + throw new CustomException(ErrorCode.INVALID_RESERVATION_TIME); + } + } + + // 중복 예약 검증 + private void validateNoOverlappingReservations(Long placeId, LocalDate date, LocalTime endTime, LocalTime startTime) { + if (reservationRepository.existsByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter(placeId, date, endTime, startTime)) { + throw new CustomException(ErrorCode.RESERVATION_ALREADY_EXISTS); + } + } + + // 특정 날짜의 실제 운영 시간 계산 + private OperatingHours getOperatingHoursFor(Place place, LocalDate date) { + Optional unavailableDayOpt = unavailableDayRepository.findByPlaceIdAndUnavailableDate(place.getId(), date); + + if (unavailableDayOpt.isPresent()) { + PlaceUnavailableDay unavailableDay = unavailableDayOpt.get(); + if (unavailableDay.getStartTimeOverride() == null) { + throw new CustomException(ErrorCode.RESERVATION_NOT_POSSIBLE_ON_DAY); + } + return new OperatingHours(unavailableDay.getStartTimeOverride(), unavailableDay.getEndTimeOverride()); + } else { + return new OperatingHours(place.getOpenTime(), place.getCloseTime()); + } + } + + // 운영 시간을 담는 간단한 레코드 + private record OperatingHours(LocalTime openTime, LocalTime closeTime) {} + + // 자기 자신을 제외하고 중복 예약을 확인하는 메서드 + private void validateNoOverlappingForUpdate(Reservation reservation, LocalDate newDate, LocalTime newEndTime, LocalTime newStartTime) { + if (reservationRepository.existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAfter( + reservation.getPlace().getId(), + newDate, + reservation.getId(), + newEndTime, + newStartTime + )) { + throw new CustomException(ErrorCode.RESERVATION_ALREADY_EXISTS); + } + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/domain/.gitkeep b/src/main/java/com/kernellabs/kernellabs/domain/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Place.java b/src/main/java/com/kernellabs/kernellabs/domain/Place.java new file mode 100644 index 0000000..85095df --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/Place.java @@ -0,0 +1,57 @@ +package com.kernellabs.kernellabs.domain; + +import com.kernellabs.kernellabs.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import java.time.LocalTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Place extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + @Column(nullable = false) + private String address; + + @Column(nullable = false) + private LocalTime openTime; + + @Column(nullable = false) + private LocalTime closeTime; + + @Column + private String thumbnailUrl; + + @Lob + private String description; + + @Column + private Integer unitPrice; + + @Builder + public Place(String name, String address, LocalTime openTime, LocalTime closeTime, String thumbnailUrl, String description, Integer unitPrice) { + this.name = name; + this.address = address; + this.openTime = openTime; + this.closeTime = closeTime; + this.thumbnailUrl = thumbnailUrl; + this.description = description; + this.unitPrice = unitPrice; + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java b/src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java new file mode 100644 index 0000000..2c89aa5 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/PlaceUnavailableDay.java @@ -0,0 +1,49 @@ +package com.kernellabs.kernellabs.domain; + +import com.kernellabs.kernellabs.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDate; +import java.time.LocalTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PlaceUnavailableDay extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; + + @Column(nullable = false) + private LocalDate unavailableDate; + + private String reason; + + // 해당 날짜에 특별히 적용할 운영 시간 (null이면 장소의 기본 시간을 따름) + private LocalTime startTimeOverride; + private LocalTime endTimeOverride; + + @Builder + public PlaceUnavailableDay(Place place, LocalDate unavailableDate, String reason, LocalTime startTimeOverride, LocalTime endTimeOverride) { + this.place = place; + this.unavailableDate = unavailableDate; + this.reason = reason; + this.startTimeOverride = startTimeOverride; + this.endTimeOverride = endTimeOverride; + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java b/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java new file mode 100644 index 0000000..3b49e4b --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/domain/Reservation.java @@ -0,0 +1,75 @@ +package com.kernellabs.kernellabs.domain; + +import com.kernellabs.kernellabs.global.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Reservation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "place_id", nullable = false) + private Place place; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private LocalDate reservationDate; + + @Column(nullable = false) + private LocalTime startTime; + + @Column(nullable = false) + private LocalTime endTime; + + @Builder + public Reservation(Place place, String password, LocalDate reservationDate, LocalTime startTime, LocalTime endTime) { + this.place = place; + this.password = password; + this.reservationDate = reservationDate; + this.startTime = startTime; + this.endTime = endTime; + } + + public static Reservation create(Place place, String password, LocalDate date, List timeSlots) { + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm"); + LocalTime startTime = LocalTime.parse(timeSlots.get(0), timeFormatter); + LocalTime endTime = LocalTime.parse(timeSlots.get(timeSlots.size() - 1), timeFormatter).plusHours(1); + + return Reservation.builder() + .place(place) + .password(password) + .reservationDate(date) + .startTime(startTime) + .endTime(endTime) + .build(); + } + + public void updateTimes(LocalDate newDate, LocalTime newStartTime, LocalTime newEndTime) { + this.reservationDate = newDate; + this.startTime = newStartTime; + this.endTime = newEndTime; + } + +} diff --git a/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java b/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java index e2b1793..faff81a 100644 --- a/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java +++ b/src/main/java/com/kernellabs/kernellabs/global/exception/ErrorCode.java @@ -28,7 +28,14 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR("9999", "서버 내부 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), EXTERNAL_SERVICE_ERROR("9901", "외부 서비스 연동 중 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR), - ; + // custom error + PLACE_NOT_FOUND("2001", "해당 장소를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), + TIME_SLOTS_EMPTY("2002", "예약 시간을 선택해주세요.", HttpStatus.BAD_REQUEST ), + INVALID_TIME_SLOT_SEQUENCE("2003", "예약 시간은 연속되어야 합니다.", HttpStatus.BAD_REQUEST), + RESERVATION_NOT_POSSIBLE_ON_DAY("2004", "선택하신 날짜는 예약이 불가능합니다.", HttpStatus.CONFLICT), + INVALID_RESERVATION_TIME("2005", "예약 요청 시간이 운영 시간 범위를 벗어납니다.", HttpStatus.BAD_REQUEST), + RESERVATION_ALREADY_EXISTS("2006", "해당 시간에 이미 예약이 존재합니다.", HttpStatus.CONFLICT), + INVALID_PASSWORD("2007", "비밀번호가 일치하지 않습니다.", HttpStatus.FORBIDDEN); private final HttpStatus status; private final String code; diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java new file mode 100644 index 0000000..11d0c2e --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceRepository.java @@ -0,0 +1,10 @@ +package com.kernellabs.kernellabs.infrastructure.repository; + +import com.kernellabs.kernellabs.domain.Place; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PlaceRepository extends JpaRepository { + +} diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java new file mode 100644 index 0000000..bd80b65 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/PlaceUnavailableDayRepository.java @@ -0,0 +1,12 @@ +package com.kernellabs.kernellabs.infrastructure.repository; + +import com.kernellabs.kernellabs.domain.PlaceUnavailableDay; +import java.time.LocalDate; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PlaceUnavailableDayRepository extends JpaRepository { + Optional findByPlaceIdAndUnavailableDate(Long placeId, LocalDate date); +} diff --git a/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java new file mode 100644 index 0000000..7606690 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/infrastructure/repository/ReservationRepository.java @@ -0,0 +1,30 @@ +package com.kernellabs.kernellabs.infrastructure.repository; + +import com.kernellabs.kernellabs.domain.Reservation; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReservationRepository extends JpaRepository { + boolean existsByPlaceIdAndReservationDateAndStartTimeBeforeAndEndTimeAfter( + Long placeId, + LocalDate date, + LocalTime endTime, + LocalTime startTime + ); + + // 예약 변경 시, 겹치는 예약이 있는지 확인 (자기 자신 제외) + boolean existsByPlaceIdAndReservationDateAndIdNotAndStartTimeBeforeAndEndTimeAfter( + Long placeId, + LocalDate date, + Long reservationId, + LocalTime endTime, + LocalTime startTime + ); +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java new file mode 100644 index 0000000..51445d0 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/controller/ReservationController.java @@ -0,0 +1,53 @@ +package com.kernellabs.kernellabs.presentation.controller; + +import com.kernellabs.kernellabs.application.ReservationService; +import com.kernellabs.kernellabs.global.common.ApiResponse; +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.response.ReservationResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +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.RestController; + +@RestController +@RequestMapping("/reservations") +@RequiredArgsConstructor +public class ReservationController { + + private final ReservationService reservationService; + + @PostMapping("") + public ResponseEntity> createReservation(@RequestBody @Valid ReservationRequest request) { + ReservationResponse response = reservationService.createReservation(request); + return ResponseEntity.ok(ApiResponse.success(response)); + } + + @GetMapping("/{reservationId}") + public ResponseEntity> getReservation(@PathVariable Long reservationId) { + ReservationResponse response = reservationService.getReservation(reservationId); + return ResponseEntity.ok(ApiResponse.success(response)); + } + + @PatchMapping("/{reservationId}") + public ResponseEntity> updateReservation(@PathVariable Long reservationId, + @Valid @RequestBody ReservationUpdateRequest request) { + ReservationResponse response = reservationService.updateReservation(reservationId, request); + return ResponseEntity.ok(ApiResponse.success(response)); + } + + @DeleteMapping("/{reservationId}") + public ResponseEntity deleteReservation(@PathVariable Long reservationId, + @Valid @RequestBody ReservationDeleteRequest request) { + reservationService.deleteReservation(reservationId, request); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java new file mode 100644 index 0000000..6601a9c --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationDeleteRequest.java @@ -0,0 +1,15 @@ +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 ReservationDeleteRequest { + + @NotBlank(message = "비밀번호는 필수입니다.") + @Pattern(regexp = "\\d{4}", message = "비밀번호는 4자리 숫자여야 합니다.") + private String password; +} \ No newline at end of file diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java new file mode 100644 index 0000000..f10a6f1 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationRequest.java @@ -0,0 +1,32 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationRequest { + + @NotNull(message = "장소 ID는 필수입니다.") + private Long placeId; + + @NotBlank(message = "비밀번호는 필수입니다.") + @Pattern(regexp = "\\d{4}", message = "비밀번호는 4자리 숫자여야 합니다.") + private String password; + + @NotNull(message = "예약 날짜는 필수입니다.") + @FutureOrPresent(message = "과거 날짜는 예약할 수 없습니다.") + private LocalDate reservationDate; + + @NotEmpty(message = "예약 시간은 최소 1개 이상 선택해야 합니다.") + @Size(max = 3, message = "예약은 최대 3시간까지 가능합니다.") + private List timeSlots; +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java new file mode 100644 index 0000000..c7a4bc2 --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/request/ReservationUpdateRequest.java @@ -0,0 +1,30 @@ +package com.kernellabs.kernellabs.presentation.dto.request; + +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ReservationUpdateRequest { + + @NotBlank(message = "비밀번호는 필수입니다.") + @Pattern(regexp = "\\d{4}", message = "비밀번호는 4자리 숫자여야 합니다.") + private String password; + + @NotNull(message = "변경할 날짜를 입력해주세요.") + @FutureOrPresent(message = "과거 날짜로는 변경할 수 없습니다.") + private LocalDate newReservationDate; + + @NotEmpty(message = "변경할 예약 시간은 최소 1개 이상 선택해야 합니다.") + @Size(max = 3, message = "예약은 최대 3시간까지 가능합니다.") + private List newTimeSlots; + +} diff --git a/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java new file mode 100644 index 0000000..04aa8cc --- /dev/null +++ b/src/main/java/com/kernellabs/kernellabs/presentation/dto/response/ReservationResponse.java @@ -0,0 +1,33 @@ +package com.kernellabs.kernellabs.presentation.dto.response; + +import com.kernellabs.kernellabs.domain.Reservation; +import java.time.LocalDate; +import java.time.LocalTime; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ReservationResponse { + + private final Long reservationId; + private final Long placeId; + private final String placeName; + private final LocalDate reservationDate; + private final LocalTime startTime; + private final LocalTime endTime; + + public static ReservationResponse from(Reservation reservation) { + return ReservationResponse.builder() + .reservationId(reservation.getId()) + .placeId(reservation.getPlace().getId()) + .placeName(reservation.getPlace().getName()) + .reservationDate(reservation.getReservationDate()) + .startTime(reservation.getStartTime()) + .endTime(reservation.getEndTime()) + .build(); + } +}