Skip to content

Commit

Permalink
Merge pull request #89 from woogieon8on/feature/reservation
Browse files Browse the repository at this point in the history
Feat: 동방 예약 기능 구현
  • Loading branch information
woogieon8on authored Nov 16, 2024
2 parents 49871bb + 94f8d3f commit bcfbd1b
Show file tree
Hide file tree
Showing 13 changed files with 355 additions and 2 deletions.
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);
}

}

0 comments on commit bcfbd1b

Please sign in to comment.