diff --git a/src/main/java/kahlua/KahluaProject/apipayload/code/status/ErrorStatus.java b/src/main/java/kahlua/KahluaProject/apipayload/code/status/ErrorStatus.java index 8a9ae44..a3a6636 100644 --- a/src/main/java/kahlua/KahluaProject/apipayload/code/status/ErrorStatus.java +++ b/src/main/java/kahlua/KahluaProject/apipayload/code/status/ErrorStatus.java @@ -44,8 +44,15 @@ public enum ErrorStatus implements BaseCode { WEBSOCKET_SESSION_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "WEBSOCKET SESSION UNAUTHORIZED", "웹소켓 연결에 실패했습니다."), //게시판 에러 + IMAGE_NOT_UPLOAD(HttpStatus.BAD_REQUEST, "IMAGE_NOT_UPLOAD", "이미지 업로드 개수를 초과하였습니다."), POST_NOT_FOUND(HttpStatus.NOT_FOUND, "POST_NOT_FOUND", "존재하지 않는 게시글입니다."), - IMAGE_NOT_UPLOAD(HttpStatus.BAD_REQUEST, "IMAGE_NOT_UPLOAD", "이미지 업로드 개수를 초과하였습니다."); + IMAGE_NOT_UPLOAD(HttpStatus.BAD_REQUEST, "IMAGE_NOT_UPLOAD", "이미지 업로드 개수를 초과하였습니다."), + + // 예약 에러 + RESERVATION_NOT_FOUND(HttpStatus.NOT_FOUND, "RESERVATION NOT FOUND", "예약내역을 찾을 수 없습니다."); + + + private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/kahlua/KahluaProject/config/SecurityConfig.java b/src/main/java/kahlua/KahluaProject/config/SecurityConfig.java index 37b6c0f..a055155 100644 --- a/src/main/java/kahlua/KahluaProject/config/SecurityConfig.java +++ b/src/main/java/kahlua/KahluaProject/config/SecurityConfig.java @@ -51,6 +51,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize .requestMatchers( "/api-docs/**", "/swagger-ui/**", "/swagger-ui.html/**", "/v3/api-docs/**", "/swagger-ui/index.html#/**").permitAll() .requestMatchers("/v1/auth/sign-out/**", "v1/auth/recreate/**","/v1/user/**", "/v1/admin/**").authenticated() + .requestMatchers("v1/reservation/**").hasAnyAuthority("KAHLUA", "ADMIN") .anyRequest().permitAll()) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(exceptionFilter, JwtFilter.class); diff --git a/src/main/java/kahlua/KahluaProject/controller/ReservationController.java b/src/main/java/kahlua/KahluaProject/controller/ReservationController.java new file mode 100644 index 0000000..c69d642 --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/controller/ReservationController.java @@ -0,0 +1,71 @@ +package kahlua.KahluaProject.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import kahlua.KahluaProject.apipayload.ApiResponse; +import kahlua.KahluaProject.dto.reservation.request.ReservationProceedRequest; +import kahlua.KahluaProject.dto.reservation.request.ReservationRequest; +import kahlua.KahluaProject.dto.reservation.response.ReservationListResponse; +import kahlua.KahluaProject.dto.reservation.response.ReservationResponse; +import kahlua.KahluaProject.security.AuthDetails; +import kahlua.KahluaProject.service.ReservationService; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.messaging.handler.annotation.*; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.Map; + +@Tag(name = "동방 예약", description = "동방 예약 기능 관련 API") +@RestController +@RequiredArgsConstructor +public class ReservationController { + + private final ReservationService reservationService; + + // 예약 시간 선택한 사람에게 우선권 부여 + @MessageMapping("/reserve.proceed/{date}") + @SendTo("/topic/public/{date}") + public ReservationResponse proceed(@DestinationVariable String reservationDate, + @Header("simpSessionAttributes") Map simpSessionAttributes, + @Payload ReservationProceedRequest reservationProceedRequest) { + + return reservationService.proceed(reservationProceedRequest, reservationDate, simpSessionAttributes); + } + + // 예약 확정 후 예약내역 DB에 저장 + @MessageMapping("/reserve.complete/{date}") + @SendTo("/topic/public/{date}") + public ReservationResponse complete(@DestinationVariable String reservationDate, + @Header("simpSessionAttributes") Map simpSessionAttributes, + @Payload ReservationRequest reservationRequest) { + + return reservationService.save(reservationRequest, reservationDate, simpSessionAttributes); + } + + @GetMapping("/v1/reservation") + @Operation(summary = "날짜별 예약내역 목록 조회", description = "지정한 날짜에 해당하는 예약내역 목록을 사용 시작 시간 오름차순으로 조회합니다." + + "
쿼리 파라미터 날짜 형식은 yyyy-MM-dd 입니다") + public ApiResponse getReservationListByDate(@RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) { + + return ApiResponse.onSuccess(reservationService.getByDate(date)); + } + + @GetMapping("/v1/reservation/check") + @Operation(summary = "사용자 예약내역 조회", description = "사용자의 전체 예약내역을 예약 날짜 오름차순으로 조회합니다." + + "
같은 날짜에 대해서는 시작 시간 오름차순으로 정렬합니다.") + public ApiResponse getReservationList(@AuthenticationPrincipal AuthDetails authDetails) { + + return ApiResponse.onSuccess(reservationService.getByUser(authDetails.user())); + } + + @DeleteMapping("/v1/reservation/check/{reservationId}") + @Operation(summary = "예약내역 삭제", description = "지정한 예약내역을 삭제합니다.") + public ApiResponse delete(@PathVariable Long reservationId) { + + reservationService.delete(reservationId); + return ApiResponse.onSuccess("예약내역 삭제 성공"); + } +} diff --git a/src/main/java/kahlua/KahluaProject/converter/ReservationConverter.java b/src/main/java/kahlua/KahluaProject/converter/ReservationConverter.java new file mode 100644 index 0000000..71d4f97 --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/converter/ReservationConverter.java @@ -0,0 +1,41 @@ +package kahlua.KahluaProject.converter; + +import kahlua.KahluaProject.domain.reservation.Reservation; +import kahlua.KahluaProject.domain.reservation.ReservationStatus; +import kahlua.KahluaProject.domain.user.User; +import kahlua.KahluaProject.dto.reservation.request.ReservationRequest; +import kahlua.KahluaProject.dto.reservation.response.ReservationResponse; + +import java.time.LocalDate; + +public class ReservationConverter { + + + public static Reservation toReservation(ReservationRequest reservationRequest, User user, LocalDate reservationDate, ReservationStatus status) { + + return Reservation.builder() + .user(user) + .type(reservationRequest.type()) + .clubRoomUsername(reservationRequest.clubroomUsername()) + .reservationDate(reservationDate) + .startTime(reservationRequest.startTime()) + .endTime(reservationRequest.endTime()) + .status(status) + .build(); + } + public static ReservationResponse toReservationResponse(Reservation reservation, String email) { + + return ReservationResponse.builder() + .reservationId(reservation.getId()) + .email(email) + .type(reservation.getType()) + .clubroomUsername(reservation.getClubRoomUsername()) + .reservationDate(reservation.getReservationDate()) + .startTime(reservation.getStartTime()) + .endTime(reservation.getEndTime()) + .status(reservation.getStatus()) + .build(); + + + } +} diff --git a/src/main/java/kahlua/KahluaProject/domain/reservation/ReservationStatus.java b/src/main/java/kahlua/KahluaProject/domain/reservation/ReservationStatus.java index ece18fc..4666c5c 100644 --- a/src/main/java/kahlua/KahluaProject/domain/reservation/ReservationStatus.java +++ b/src/main/java/kahlua/KahluaProject/domain/reservation/ReservationStatus.java @@ -2,5 +2,5 @@ public enum ReservationStatus { - PROCEEDING, RESERVED, CANCELLED + PROCEEDING, RESERVED } diff --git a/src/main/java/kahlua/KahluaProject/dto/reservation/request/ReservationProceedRequest.java b/src/main/java/kahlua/KahluaProject/dto/reservation/request/ReservationProceedRequest.java new file mode 100644 index 0000000..7cbb4a2 --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/dto/reservation/request/ReservationProceedRequest.java @@ -0,0 +1,13 @@ +package kahlua.KahluaProject.dto.reservation.request; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.time.LocalTime; + +public record ReservationProceedRequest( + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone = "Asia/Seoul") + LocalTime startTime, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone = "Asia/Seoul") + LocalTime endTime +) { +} diff --git a/src/main/java/kahlua/KahluaProject/dto/reservation/request/ReservationRequest.java b/src/main/java/kahlua/KahluaProject/dto/reservation/request/ReservationRequest.java new file mode 100644 index 0000000..0b996ec --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/dto/reservation/request/ReservationRequest.java @@ -0,0 +1,16 @@ +package kahlua.KahluaProject.dto.reservation.request; + +import com.fasterxml.jackson.annotation.JsonFormat; +import kahlua.KahluaProject.domain.reservation.ReservationType; + +import java.time.LocalTime; + +public record ReservationRequest( + ReservationType type, + String clubroomUsername, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone = "Asia/Seoul") + LocalTime startTime, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone = "Asia/Seoul") + LocalTime endTime +) { +} diff --git a/src/main/java/kahlua/KahluaProject/dto/reservation/response/ReservationListResponse.java b/src/main/java/kahlua/KahluaProject/dto/reservation/response/ReservationListResponse.java new file mode 100644 index 0000000..82df59c --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/dto/reservation/response/ReservationListResponse.java @@ -0,0 +1,11 @@ +package kahlua.KahluaProject.dto.reservation.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +public record ReservationListResponse( + @Schema(description = "예약내역 목록") + List reservationResponseList +) { +} diff --git a/src/main/java/kahlua/KahluaProject/dto/reservation/response/ReservationResponse.java b/src/main/java/kahlua/KahluaProject/dto/reservation/response/ReservationResponse.java new file mode 100644 index 0000000..f65013b --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/dto/reservation/response/ReservationResponse.java @@ -0,0 +1,36 @@ +package kahlua.KahluaProject.dto.reservation.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import kahlua.KahluaProject.domain.reservation.ReservationStatus; +import kahlua.KahluaProject.domain.reservation.ReservationType; +import lombok.Builder; + +import java.time.LocalDate; +import java.time.LocalTime; + +@Builder +public record ReservationResponse( + @Schema(description = "예약 id", example = "1") + Long reservationId, + @Schema(description = "예약자 이메일", example = "kahlua@kahlua.com") + String email, + @Schema(description = "예약 유형", example = "TEAM") + ReservationType type, + @Schema(description = "예약자명", example = "깔루아팀") + String clubroomUsername, + + @Schema(description = "예약 날짜", example = "2024-01-01") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + LocalDate reservationDate, + @Schema(description = "사용 시작 시간", example = "10:00:00") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm", timezone = "Asia/Seoul") + LocalTime startTime, + @Schema(description = "사용 종료 시간", example = "11:00:00") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone = "Asia/Seoul") + LocalTime endTime, + + @Schema(description = "예약 상태", example = "RESERVED") + ReservationStatus status +) { +} diff --git a/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationCustomRepository.java b/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationCustomRepository.java new file mode 100644 index 0000000..bbe91de --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationCustomRepository.java @@ -0,0 +1,13 @@ +package kahlua.KahluaProject.repository.reservation; + +import kahlua.KahluaProject.domain.reservation.Reservation; +import kahlua.KahluaProject.domain.user.User; + +import java.time.LocalDate; +import java.util.List; + +public interface ReservationCustomRepository { + + List findByDate(LocalDate date); + List findByUser(User user); +} diff --git a/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationCustomRepositoryImpl.java b/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationCustomRepositoryImpl.java new file mode 100644 index 0000000..98db251 --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationCustomRepositoryImpl.java @@ -0,0 +1,39 @@ +package kahlua.KahluaProject.repository.reservation; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import kahlua.KahluaProject.domain.reservation.Reservation; +import kahlua.KahluaProject.domain.user.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.util.List; + +import static kahlua.KahluaProject.domain.reservation.QReservation.reservation; + +@Repository +@RequiredArgsConstructor +public class ReservationCustomRepositoryImpl implements ReservationCustomRepository{ + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findByDate(LocalDate date) { + + return jpaQueryFactory + .selectFrom(reservation) + .where(reservation.reservationDate.eq(date)) + .orderBy(reservation.startTime.asc()) + .fetch(); + } + + @Override + public List findByUser(User user) { + + return jpaQueryFactory + .selectFrom(reservation) + .where(reservation.user.eq(user)) + .orderBy(reservation.reservationDate.asc(), reservation.startTime.asc()) + .fetch(); + } +} diff --git a/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationRepository.java b/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationRepository.java new file mode 100644 index 0000000..a519879 --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/repository/reservation/ReservationRepository.java @@ -0,0 +1,7 @@ +package kahlua.KahluaProject.repository.reservation; + +import kahlua.KahluaProject.domain.reservation.Reservation; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReservationRepository extends JpaRepository, ReservationCustomRepository { +} diff --git a/src/main/java/kahlua/KahluaProject/service/ReservationService.java b/src/main/java/kahlua/KahluaProject/service/ReservationService.java new file mode 100644 index 0000000..40900b8 --- /dev/null +++ b/src/main/java/kahlua/KahluaProject/service/ReservationService.java @@ -0,0 +1,98 @@ +package kahlua.KahluaProject.service; + +import jakarta.transaction.Transactional; +import kahlua.KahluaProject.apipayload.code.status.ErrorStatus; +import kahlua.KahluaProject.domain.reservation.Reservation; +import kahlua.KahluaProject.domain.reservation.ReservationStatus; +import kahlua.KahluaProject.domain.user.User; +import kahlua.KahluaProject.dto.reservation.request.ReservationProceedRequest; +import kahlua.KahluaProject.dto.reservation.request.ReservationRequest; +import kahlua.KahluaProject.dto.reservation.response.ReservationListResponse; +import kahlua.KahluaProject.dto.reservation.response.ReservationResponse; +import kahlua.KahluaProject.exception.GeneralException; +import kahlua.KahluaProject.repository.reservation.ReservationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static kahlua.KahluaProject.converter.ReservationConverter.*; + +@Service +@RequiredArgsConstructor +public class ReservationService { + + private final ReservationRepository reservationRepository; + private final UserService userService; + + public ReservationResponse proceed(ReservationProceedRequest reservationProceedRequest, String reservationDate, Map header) { + + String email = getValueFromHeader(header, "email"); + + return ReservationResponse.builder() + .email(email) + .reservationDate(toLocalDate(reservationDate)) + .startTime(reservationProceedRequest.startTime()) + .endTime(reservationProceedRequest.endTime()) + .status(ReservationStatus.PROCEEDING) + .build(); + } + + @Transactional + public ReservationResponse save(ReservationRequest reservationRequest, String reservationDate, Map header) { + + String email = getValueFromHeader(header, "email"); + User user = userService.getUserByEmail(email); + + Reservation reservation = toReservation(reservationRequest, user, toLocalDate(reservationDate), ReservationStatus.RESERVED); + Reservation savedReservation = reservationRepository.save(reservation); + + return toReservationResponse(savedReservation, email); + } + + @Transactional + public ReservationListResponse getByDate(LocalDate date) { + + List reservationList = reservationRepository.findByDate(date); + + List reservationResponseList = reservationList.stream() + .map(reservation -> toReservationResponse(reservation, reservation.getUser().getEmail())) + .collect(Collectors.toList()); + + return new ReservationListResponse(reservationResponseList); + } + + public ReservationListResponse getByUser(User user) { + + List reservationList = reservationRepository.findByUser(user); + + List reservationResponseList = reservationList.stream() + .map(reservation -> toReservationResponse(reservation, user.getEmail())) + .collect(Collectors.toList()); + + return new ReservationListResponse(reservationResponseList); + } + + @Transactional + public void delete(Long reservationId) { + Reservation reservation = reservationRepository.findById(reservationId) + .orElseThrow(() -> new GeneralException(ErrorStatus.RESERVATION_NOT_FOUND)); + + reservationRepository.delete(reservation); + } + + // String to LocalDateTime + private LocalDate toLocalDate(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return LocalDate.parse(date, formatter); + } + + private String getValueFromHeader(Map header, String key) { + return (String)header.get(key); + } + +}