Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 동방 예약 기능 구현 #89

Merged
merged 9 commits into from
Nov 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, Object> simpSessionAttributes,
@Payload ReservationRequest reservationRequest) {

return reservationService.save(reservationRequest, reservationDate, simpSessionAttributes);
}

@GetMapping("/v1/reservation")
@Operation(summary = "날짜별 예약내역 목록 조회", description = "지정한 날짜에 해당하는 예약내역 목록을 사용 시작 시간 오름차순으로 조회합니다." +
"<br> 쿼리 파라미터 날짜 형식은 yyyy-MM-dd 입니다")
public ApiResponse<ReservationListResponse> getReservationListByDate(@RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {

return ApiResponse.onSuccess(reservationService.getByDate(date));
}

@GetMapping("/v1/reservation/check")
@Operation(summary = "사용자 예약내역 조회", description = "사용자의 전체 예약내역을 예약 날짜 오름차순으로 조회합니다." +
"<br> 같은 날짜에 대해서는 시작 시간 오름차순으로 정렬합니다.")
public ApiResponse<ReservationListResponse> 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("예약내역 삭제 성공");
}
}
Original file line number Diff line number Diff line change
@@ -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();


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public enum ReservationStatus {

PROCEEDING, RESERVED, CANCELLED
PROCEEDING, RESERVED
}
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
@@ -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<ReservationResponse> reservationResponseList
) {
}
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]")
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
) {
}
Original file line number Diff line number Diff line change
@@ -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<Reservation> findByDate(LocalDate date);
List<Reservation> findByUser(User user);
}
Original file line number Diff line number Diff line change
@@ -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<Reservation> findByDate(LocalDate date) {

return jpaQueryFactory
.selectFrom(reservation)
.where(reservation.reservationDate.eq(date))
.orderBy(reservation.startTime.asc())
.fetch();
}

@Override
public List<Reservation> findByUser(User user) {

return jpaQueryFactory
.selectFrom(reservation)
.where(reservation.user.eq(user))
.orderBy(reservation.reservationDate.asc(), reservation.startTime.asc())
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -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<Reservation, Long>, ReservationCustomRepository {
}
98 changes: 98 additions & 0 deletions src/main/java/kahlua/KahluaProject/service/ReservationService.java
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, Object> 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<Reservation> reservationList = reservationRepository.findByDate(date);

List<ReservationResponse> reservationResponseList = reservationList.stream()
.map(reservation -> toReservationResponse(reservation, reservation.getUser().getEmail()))
.collect(Collectors.toList());

return new ReservationListResponse(reservationResponseList);
}

public ReservationListResponse getByUser(User user) {

List<Reservation> reservationList = reservationRepository.findByUser(user);

List<ReservationResponse> 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<String, Object> header, String key) {
return (String)header.get(key);
}

}
Loading