diff --git a/ticket/build.gradle b/ticket/build.gradle index a2d3c42..8132f27 100644 --- a/ticket/build.gradle +++ b/ticket/build.gradle @@ -22,6 +22,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' + // validation + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' diff --git a/ticket/src/main/java/comento/backend/ticket/config/ErrorCode.java b/ticket/src/main/java/comento/backend/ticket/config/ErrorCode.java new file mode 100644 index 0000000..8769d25 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/ErrorCode.java @@ -0,0 +1,25 @@ +package comento.backend.ticket.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ErrorCode { + /** + * @see : 예외케이스 정의 + */ + + INVALID_VALUE(400, HttpStatus.BAD_REQUEST, "BAD REQUEST", "잘못된 요청입니다. 다시 요청해주세요."), + NO_USER(401, HttpStatus.UNAUTHORIZED,"UNAUTHORIZED", "등록되지 않은 사용자입니다"), + NO_DATA(404, HttpStatus.NOT_FOUND, "NOT FOUND ERROR", "요청할 수 없는 리소스입니다."), + INVALID_USER(409, HttpStatus.CONFLICT, "CONFLICT", "이미 존재하는 사용자입니다."), + INVALID_SEAT(409, HttpStatus.CONFLICT, "CONFLICT", "이미 예약된 좌석입니다."), + SERVER_ERROR(500, HttpStatus.INTERNAL_SERVER_ERROR, "INTERVAL_SERVER ERROR", "서버 에러입니다."); + + private final Integer status; + private final HttpStatus httpStatus; + private final String message; + private final String reason; +} diff --git a/ticket/src/main/java/comento/backend/ticket/config/ErrorResponse.java b/ticket/src/main/java/comento/backend/ticket/config/ErrorResponse.java new file mode 100644 index 0000000..93d3254 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/ErrorResponse.java @@ -0,0 +1,24 @@ +package comento.backend.ticket.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +public class ErrorResponse{ + private Integer status; + private String message; + private String reason; + + //공통된 Error Message를 전송할 때 사용 + public static ErrorResponse res(final Integer status, final String message, final String reason){ + return ErrorResponse.builder() + .status(status) + .message(message) + .reason(reason) + .build(); + } +} + diff --git a/ticket/src/main/java/comento/backend/ticket/config/GlobalExceptionHandler.java b/ticket/src/main/java/comento/backend/ticket/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..b02f14d --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/GlobalExceptionHandler.java @@ -0,0 +1,78 @@ +package comento.backend.ticket.config; + +import com.fasterxml.jackson.core.JsonParseException; +import comento.backend.ticket.config.customException.DuplicatedException; +import comento.backend.ticket.config.customException.NoAuthException; +import comento.backend.ticket.config.customException.NotFoundDataException; +import javassist.NotFoundException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import javax.validation.ConstraintViolationException; +import java.util.NoSuchElementException; + +@RestControllerAdvice //@Conroller 전역에서 발생 가능한 예외를 잡아 처리 +@Slf4j +public class GlobalExceptionHandler { + private static ErrorCode errorCode; + + /** + * 특정 Exception을 지정하여 별도로 처리 + */ + + //ConstraintViolationException.class 는 유효성 검사 실패시 (@Validated) + @ExceptionHandler({JsonParseException.class, + MethodArgumentNotValidException.class, + ConstraintViolationException.class, + MethodArgumentTypeMismatchException.class, + MissingServletRequestParameterException.class}) + public static ResponseEntity missMatchExceptionHandler(Throwable t){ + errorCode = ErrorCode.INVALID_VALUE; + log.error(errorCode.getStatus() + " " + errorCode.getMessage(), t); + return new ResponseEntity<>(ErrorResponse.res(errorCode.getStatus(), errorCode.getMessage(), errorCode.getReason()), + errorCode.getHttpStatus()); + } + + @ExceptionHandler(NoAuthException.class) + public static ResponseEntity noAuthExceptionHandler(NoAuthException e){ + errorCode = ErrorCode.NO_USER; + log.error(errorCode.getStatus() + errorCode.getMessage(), e); + return new ResponseEntity<>(ErrorResponse.res(errorCode.getStatus(), errorCode.getMessage(), errorCode.getReason()), + errorCode.getHttpStatus()); + } + + @ExceptionHandler({NoSuchElementException.class, NotFoundException.class, NotFoundDataException.class}) + public static ResponseEntity notFoundExceptionHandler(Throwable t){ + errorCode = ErrorCode.NO_DATA; + log.error(errorCode.getStatus() + " " + errorCode.getMessage(), t); + return new ResponseEntity<>(ErrorResponse.res(errorCode.getStatus(), errorCode.getMessage(), errorCode.getReason()), + errorCode.getHttpStatus()); + } + + @ExceptionHandler(DuplicatedException.class) + public static ResponseEntity duplicateExceptionHandler(DuplicatedException e){ + if (e.getMessage() == "UserService") { //호출된 곳이 UserService + errorCode = ErrorCode.INVALID_USER; + log.error(errorCode.getStatus() + " " + errorCode.getMessage(), e); + }else{ //좌석 예약 시 + errorCode = ErrorCode.INVALID_SEAT; + log.error(errorCode.getStatus() + " " + errorCode.getMessage(), e); + } + return new ResponseEntity<>(ErrorResponse.res(errorCode.getStatus(), errorCode.getMessage(), errorCode.getReason()), + errorCode.getHttpStatus()); + } + + @ExceptionHandler({IllegalStateException.class, RuntimeException.class}) + public static ResponseEntity IllExceptionHandler(Throwable t){ + errorCode = ErrorCode.SERVER_ERROR; + log.error(errorCode.getStatus() + " " + errorCode.getMessage(), t); + return new ResponseEntity<>(ErrorResponse.res(errorCode.getStatus(), errorCode.getMessage(), errorCode.getReason()), + errorCode.getHttpStatus()); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/config/SuccessCode.java b/ticket/src/main/java/comento/backend/ticket/config/SuccessCode.java new file mode 100644 index 0000000..61701c7 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/SuccessCode.java @@ -0,0 +1,17 @@ +package comento.backend.ticket.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SuccessCode { + + OK(200, HttpStatus.OK, "OK"), + CREATED(201, HttpStatus.CREATED, "CREATED"); + + private final Integer status; + private final HttpStatus httpStatus; + private final String message; +} diff --git a/ticket/src/main/java/comento/backend/ticket/config/SuccessResponse.java b/ticket/src/main/java/comento/backend/ticket/config/SuccessResponse.java new file mode 100644 index 0000000..e491cea --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/SuccessResponse.java @@ -0,0 +1,24 @@ +package comento.backend.ticket.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@AllArgsConstructor +@Builder +public class SuccessResponse { + private Integer status; + private String message; + private Object data; + + //공통된 Success Message를 전송할 때 사용 + public static SuccessResponse res(final Integer status, final String message, final Object data){ + return SuccessResponse.builder() + .status(status) + .message(message) + .data(data) + .build(); + } + +} diff --git a/ticket/src/main/java/comento/backend/ticket/config/customException/DuplicatedException.java b/ticket/src/main/java/comento/backend/ticket/config/customException/DuplicatedException.java new file mode 100644 index 0000000..bcf6588 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/customException/DuplicatedException.java @@ -0,0 +1,10 @@ +package comento.backend.ticket.config.customException; + +public class DuplicatedException extends RuntimeException{ + public DuplicatedException() { + } + + public DuplicatedException(String message) { + super(message); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/config/customException/NoAuthException.java b/ticket/src/main/java/comento/backend/ticket/config/customException/NoAuthException.java new file mode 100644 index 0000000..f264136 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/customException/NoAuthException.java @@ -0,0 +1,10 @@ +package comento.backend.ticket.config.customException; + +public class NoAuthException extends RuntimeException{ + public NoAuthException() { + } + + public NoAuthException(String message) { + super(message); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/config/customException/NotFoundDataException.java b/ticket/src/main/java/comento/backend/ticket/config/customException/NotFoundDataException.java new file mode 100644 index 0000000..c53f237 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/config/customException/NotFoundDataException.java @@ -0,0 +1,10 @@ +package comento.backend.ticket.config.customException; + +public class NotFoundDataException extends RuntimeException{ + public NotFoundDataException() { + } + + public NotFoundDataException(String message) { + super(message); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/controller/BookingController.java b/ticket/src/main/java/comento/backend/ticket/controller/BookingController.java new file mode 100644 index 0000000..81a846c --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/controller/BookingController.java @@ -0,0 +1,45 @@ +package comento.backend.ticket.controller; + +import comento.backend.ticket.config.SuccessCode; +import comento.backend.ticket.config.SuccessResponse; +import comento.backend.ticket.domain.Booking; +import comento.backend.ticket.dto.BookingDto; +import comento.backend.ticket.dto.BookingResponse; +import comento.backend.ticket.dto.BookingResponseCreated; +import comento.backend.ticket.service.BookingService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.*; + +@RestController +@RequestMapping("/api/performance/booking") +public class BookingController { + private final BookingService bookingService; + private SuccessCode successCode; + + @Autowired + public BookingController(BookingService bookingService) { + this.bookingService = bookingService; + } + + @PostMapping("") + public ResponseEntity addBooking(@Valid @RequestBody BookingDto reqBooking){ + bookingService.saveBookging(reqBooking); + BookingResponseCreated result = new BookingResponseCreated(reqBooking.getSeatType(), reqBooking.getSeatNumber()); + successCode = SuccessCode.CREATED; + return new ResponseEntity(SuccessResponse.res(successCode.getStatus(), successCode.getMessage(), result), + HttpStatus.CREATED); + } + + @GetMapping("/email/{email}") + public ResponseEntity showMyBooking(@Valid @PathVariable String email){ + List result = bookingService.getMyBooking(email); + successCode = SuccessCode.OK; + return new ResponseEntity(SuccessResponse.res(successCode.getStatus(), successCode.getMessage(), result), + HttpStatus.OK); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/controller/PerformanceController.java b/ticket/src/main/java/comento/backend/ticket/controller/PerformanceController.java new file mode 100644 index 0000000..a1c59be --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/controller/PerformanceController.java @@ -0,0 +1,61 @@ +package comento.backend.ticket.controller; + +import comento.backend.ticket.config.SuccessCode; +import comento.backend.ticket.config.SuccessResponse; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.dto.PerformanceDto; +import comento.backend.ticket.dto.PerformanceResponse; +import comento.backend.ticket.dto.SeatResponse; +import comento.backend.ticket.service.PerformanceService; +import comento.backend.ticket.service.SeatService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.*; +import java.util.Date; + +@RestController +@RequestMapping("/api/performance") +public class PerformanceController { + private final PerformanceService performanceService; + private final SeatService seatService; + private SuccessCode successCode = SuccessCode.OK; + + @Autowired + public PerformanceController(PerformanceService performanceService, SeatService seatService) { + this.performanceService = performanceService; + this.seatService = seatService; + } + + @GetMapping("/info") + public ResponseEntity showPerformanceInfo(@Valid @RequestParam(value = "date", required = true) + @DateTimeFormat(pattern = "yyyy-MM-dd") Date date, + @Valid @RequestParam(value = "title", required = false) String title) { + PerformanceDto performanceDto = new PerformanceDto(title, date); + List result = performanceService.getListPerformance(performanceDto); + + return new ResponseEntity(SuccessResponse.res(successCode.getStatus(), successCode.getMessage(), result), + HttpStatus.OK); + } + + @GetMapping("/info/seat") + public ResponseEntity showPerformanceSeatInfo(@Valid @RequestParam(value = "date", required = true) + @DateTimeFormat(pattern = "yyyy-MM-dd") Date date, + @Valid @RequestParam(value = "title", required = true) String title) { + PerformanceDto performanceDto = new PerformanceDto(title, date); + List performanceData = performanceService.getListPerformance(performanceDto); + + List seatResult = seatService.getListPerformanceSeat(performanceData.get(0)); + + return new ResponseEntity(SuccessResponse.res(successCode.getStatus(), successCode.getMessage(), seatResult), + HttpStatus.OK); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/controller/UserController.java b/ticket/src/main/java/comento/backend/ticket/controller/UserController.java new file mode 100644 index 0000000..9848d07 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/controller/UserController.java @@ -0,0 +1,32 @@ +package comento.backend.ticket.controller; + +import comento.backend.ticket.config.SuccessCode; +import comento.backend.ticket.config.SuccessResponse; +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.User; +import comento.backend.ticket.dto.UserDto; +import comento.backend.ticket.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api") +public class UserController { + private SuccessCode successCode = SuccessCode.CREATED; + private final UserService userService; + private static final String CREATED_MSG = "등록 성공"; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + @PostMapping("/signup") + public ResponseEntity addEmail(@Validated @RequestBody UserDto userDto){ + userService.saveUser(userDto); + return new ResponseEntity<>(SuccessResponse.res(successCode.getStatus(), successCode.getMessage(), CREATED_MSG), + successCode.getHttpStatus()); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/domain/Booking.java b/ticket/src/main/java/comento/backend/ticket/domain/Booking.java new file mode 100644 index 0000000..4c1b0e8 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/domain/Booking.java @@ -0,0 +1,48 @@ +package comento.backend.ticket.domain; + +import lombok.Builder; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Data +@Table(name="Booking") +public class Booking { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="booking_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "performance_id") + private Performance performance; + + @OneToOne + @JoinColumn(name = "seat_id") + private Seat seat; + + @Column(name="create_at") + @CreationTimestamp //Entity가 생성되어 저장될 때 시간이 자동 저장 + @Temporal(TemporalType.TIMESTAMP) + private Date createAt; + + public Booking(){} + + @Builder + public Booking(Long id, User user, Performance performance, Seat seat, Date createAt) { + this.id = id; + this.user = user; + this.performance = performance; + this.seat = seat; + this.createAt = createAt; + } +} + diff --git a/ticket/src/main/java/comento/backend/ticket/domain/BookingHistory.java b/ticket/src/main/java/comento/backend/ticket/domain/BookingHistory.java new file mode 100644 index 0000000..8a71a43 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/domain/BookingHistory.java @@ -0,0 +1,49 @@ +package comento.backend.ticket.domain; + +import lombok.Builder; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Data +@Table(name="BookingHistory") +public class BookingHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="bh_id") //booking_histroy id + private Long id; + + @Column(name = "is_success", columnDefinition = "boolean default false") + private Boolean isSuccess; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "performance_id") + private Performance performance; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "seat_id") + private Seat seat; + + @Column(name="create_at") + @CreationTimestamp //Entity가 생성되어 저장될 때 시간이 자동 저장 + @Temporal(TemporalType.TIMESTAMP) + private Date createAt; + + public BookingHistory(){} + + @Builder + public BookingHistory(Boolean isSuccess, User user, Performance performance, Seat seat) { + this.isSuccess = isSuccess; + this.user = user; + this.performance = performance; + this.seat = seat; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/domain/Performance.java b/ticket/src/main/java/comento/backend/ticket/domain/Performance.java new file mode 100644 index 0000000..b6dfffa --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/domain/Performance.java @@ -0,0 +1,55 @@ +package comento.backend.ticket.domain; + +import lombok.Builder; +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Data +@Table(name="Performance") +public class Performance { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="performance_id") + private Long id; + + @Column + private String title; + + @Column + private String genere; + + @Column(name="start_date") + @Temporal(TemporalType.DATE) + private Date startDate; + + @Column(name="end_date") + @Temporal(TemporalType.DATE) + private Date endDate; + + @Column(columnDefinition = "TEXT") + private String price; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(name = "running_time") + private String runningTime; + + public Performance(){} + + @Builder + public Performance(Long id, String title, String genere, Date startDate, Date endDate, String price, String description, String runningTime) { + this.id = id; + this.title = title; + this.genere = genere; + this.startDate = startDate; + this.endDate = endDate; + this.price = price; + this.description = description; + this.runningTime = runningTime; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/domain/Seat.java b/ticket/src/main/java/comento/backend/ticket/domain/Seat.java new file mode 100644 index 0000000..b5087ca --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/domain/Seat.java @@ -0,0 +1,43 @@ +package comento.backend.ticket.domain; + +import comento.backend.ticket.dto.SeatType; +import lombok.Builder; +import lombok.Data; + +import javax.persistence.*; + +@Entity +@Data +@Table(name = "Seat") +public class Seat { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "seat_id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "performance_id") + private Performance performance; + + @Column(name = "seat_type", nullable = true) + @Enumerated(EnumType.STRING) //enum 타입으로 DB에 저장 + private SeatType seatType; + + @Column(name = "seat_number", nullable = true) + private Integer seatNumber; + + @Column(name = "is_booking", columnDefinition = "boolean default false") + private boolean isBooking; + + public Seat(){} + + @Builder + public Seat(Long id, Performance performance, SeatType seatType, Integer seatNumber, boolean isBooking) { + this.id = id; + this.performance = performance; + this.seatType = seatType; + this.seatNumber = seatNumber; + this.isBooking = isBooking; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/domain/User.java b/ticket/src/main/java/comento/backend/ticket/domain/User.java new file mode 100644 index 0000000..755b7c6 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/domain/User.java @@ -0,0 +1,37 @@ +package comento.backend.ticket.domain; + +import lombok.Builder; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.springframework.data.annotation.CreatedDate; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Data +@Table(name="User") +public class User{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="user_id") + private Long id; + + @Column(nullable = true) + private String email; + + @Column(name="create_at") + @CreationTimestamp //Entity가 생성되어 저장될 때 시간이 자동 저장 + @Temporal(TemporalType.TIMESTAMP) + private Date createAt; + + public User(){} + + @Builder + public User(long id, String email, Date create_at) { + this.id = id; + this.email = email; + this.createAt = createAt; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/BookingDto.java b/ticket/src/main/java/comento/backend/ticket/dto/BookingDto.java new file mode 100644 index 0000000..2a7ff98 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/BookingDto.java @@ -0,0 +1,39 @@ +package comento.backend.ticket.dto; + +import comento.backend.ticket.domain.Booking; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import comento.backend.ticket.domain.User; +import lombok.Data; +import lombok.Getter; + +import java.util.Date; + +/* + "date" : 2021-09-04, + "title" : "뮤지컬", + "email" :"kimhyejung12@naver.com" + "seat" : "VIP석" + "seat_number" : 2 + */ + +@Getter +@Data +public class BookingDto { + private Long id; + private String title; + private Date startDate; + private String email; + private SeatType seatType; + private Integer seatNumber; + private String price; + + public Booking toEntity(final User user, final Performance performance, final Seat seat) { + return Booking.builder() + .user(user) + .performance(performance) + .seat(seat) + .build(); + } + +} \ No newline at end of file diff --git a/ticket/src/main/java/comento/backend/ticket/dto/BookingHistoryDto.java b/ticket/src/main/java/comento/backend/ticket/dto/BookingHistoryDto.java new file mode 100644 index 0000000..3b8da8d --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/BookingHistoryDto.java @@ -0,0 +1,37 @@ +package comento.backend.ticket.dto; + +import comento.backend.ticket.domain.BookingHistory; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import comento.backend.ticket.domain.User; +import lombok.Builder; +import lombok.Data; + +@Data +public class BookingHistoryDto { + private Long id; + private boolean isSuccess; + private User user; + private Performance performance; + private Seat seat; + + public BookingHistoryDto(){} + + @Builder + public BookingHistoryDto(Long id, boolean isSuccess, User user, Performance performance, Seat seat) { + this.id = id; + this.isSuccess = isSuccess; + this.user = user; + this.performance = performance; + this.seat = seat; + } + + public BookingHistory toEntity() { + return BookingHistory.builder() + .isSuccess(isSuccess) + .user(user) + .performance(performance) + .seat(seat) + .build(); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/BookingResponse.java b/ticket/src/main/java/comento/backend/ticket/dto/BookingResponse.java new file mode 100644 index 0000000..ab399fb --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/BookingResponse.java @@ -0,0 +1,26 @@ +package comento.backend.ticket.dto; + +import lombok.Data; + +import java.util.Date; + +@Data +public class BookingResponse { + private String title; + private Date startDate; + private String email; + private SeatType seatType; + private Integer seatNumber; + private String price; + + public BookingResponse(){} + + public BookingResponse(String title, Date startDate, String email, SeatType seatType, Integer seatNumber, String price) { + this.title = title; + this.startDate = startDate; + this.email = email; + this.seatType = seatType; + this.seatNumber = seatNumber; + this.price = price; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/BookingResponseCreated.java b/ticket/src/main/java/comento/backend/ticket/dto/BookingResponseCreated.java new file mode 100644 index 0000000..71d70e0 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/BookingResponseCreated.java @@ -0,0 +1,14 @@ +package comento.backend.ticket.dto; + +import lombok.Data; + +@Data +public class BookingResponseCreated { + private SeatType seatType; + private Integer seatNumber; + + public BookingResponseCreated(SeatType seatType, Integer seatNumber) { + this.seatType = seatType; + this.seatNumber = seatNumber; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/PerformanceDto.java b/ticket/src/main/java/comento/backend/ticket/dto/PerformanceDto.java new file mode 100644 index 0000000..7912006 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/PerformanceDto.java @@ -0,0 +1,35 @@ +package comento.backend.ticket.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +@Getter +@Setter +public class PerformanceDto { + private String title; + private Date startDate; + private Date endDate; + private String price; + private String genere; + private String description; + private String runningTime; + + public PerformanceDto(){} + + public PerformanceDto(String title, Date startDate) { + this.title = title; + this.startDate = startDate; + } + + public PerformanceDto(String title, Date startDate, Date endDate, String price, String genere, String description, String runningTime) { + this.title = title; + this.startDate = startDate; + this.endDate = endDate; + this.price = price; + this.genere = genere; + this.description = description; + this.runningTime = runningTime; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/PerformanceResponse.java b/ticket/src/main/java/comento/backend/ticket/dto/PerformanceResponse.java new file mode 100644 index 0000000..c823f18 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/PerformanceResponse.java @@ -0,0 +1,43 @@ +package comento.backend.ticket.dto; + +import comento.backend.ticket.domain.Performance; +import lombok.Data; + +import java.util.*; + +@Data +public class PerformanceResponse { + private Long id; + private String title; + private Date startDate; + private Date endDate; + private String price; + private String genere; + private String description; + private String runningTime; + + public PerformanceResponse(Long id, String title, Date startDate, Date endDate, String price, String genere, String description, String runningTime) { + this.id = id; + this.title = title; + this.startDate = startDate; + this.endDate = endDate; + this.price = price; + this.genere = genere; + this.description = description; + this.runningTime = runningTime; + } + + //convert + public static PerformanceResponse of(Performance performance){ + return new PerformanceResponse( + performance.getId(), + performance.getTitle(), + performance.getStartDate(), + performance.getEndDate(), + performance.getPrice(), + performance.getGenere(), + performance.getDescription(), + performance.getRunningTime() + ); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/SeatDto.java b/ticket/src/main/java/comento/backend/ticket/dto/SeatDto.java new file mode 100644 index 0000000..0006163 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/SeatDto.java @@ -0,0 +1,36 @@ +package comento.backend.ticket.dto; + +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import lombok.Builder; +import lombok.Data; + +@Data +public class SeatDto { + private Long seat_id; + private Performance performance; + private SeatType seatType; + private Integer seatNumber; + private boolean isBooking; + + public SeatDto(){}; + + @Builder + public SeatDto(Long seat_id, Performance performance, SeatType seatType, Integer seatNumber, boolean isBooking) { + this.seat_id = seat_id; + this.performance = performance; + this.seatType = seatType; + this.seatNumber = seatNumber; + this.isBooking = isBooking; + } + + public Seat toEntity(){ + return Seat.builder() + .id(seat_id) + .performance(performance) + .seatType(seatType) + .seatNumber(seatNumber) + .isBooking(isBooking) + .build(); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/SeatResponse.java b/ticket/src/main/java/comento/backend/ticket/dto/SeatResponse.java new file mode 100644 index 0000000..85d6992 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/SeatResponse.java @@ -0,0 +1,25 @@ +package comento.backend.ticket.dto; + +import comento.backend.ticket.domain.Seat; +import lombok.Data; + +@Data +public class SeatResponse { + private SeatType seatType; + private Integer seatNumber; + private boolean isBooking; + + public SeatResponse(SeatType seatType, Integer seatNumber, boolean isBooking) { + this.seatType = seatType; + this.seatNumber = seatNumber; + this.isBooking = isBooking; + } + + public static SeatResponse of(Seat seat){ + return new SeatResponse( + seat.getSeatType(), + seat.getSeatNumber(), + seat.isBooking() + ); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/SeatType.java b/ticket/src/main/java/comento/backend/ticket/dto/SeatType.java new file mode 100644 index 0000000..33b9127 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/SeatType.java @@ -0,0 +1,9 @@ +package comento.backend.ticket.dto; + +public enum SeatType { + VIP, + S, + A, + B, + C +} diff --git a/ticket/src/main/java/comento/backend/ticket/dto/UserDto.java b/ticket/src/main/java/comento/backend/ticket/dto/UserDto.java new file mode 100644 index 0000000..f837c3d --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/dto/UserDto.java @@ -0,0 +1,26 @@ +package comento.backend.ticket.dto; + +import comento.backend.ticket.domain.User; +import lombok.Data; +import lombok.Getter; + +import javax.validation.constraints.Email; + +@Getter +@Data +public class UserDto { + @Email + private String email; + + public UserDto(){} + + public UserDto(String email) { + this.email = email; + } + + public User toEntity(){ + return User.builder() + .email(email) + .build(); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/repository/BookingHistoryRepository.java b/ticket/src/main/java/comento/backend/ticket/repository/BookingHistoryRepository.java new file mode 100644 index 0000000..bac48e6 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/repository/BookingHistoryRepository.java @@ -0,0 +1,9 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.BookingHistory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BookingHistoryRepository extends JpaRepository { +} diff --git a/ticket/src/main/java/comento/backend/ticket/repository/BookingRepository.java b/ticket/src/main/java/comento/backend/ticket/repository/BookingRepository.java new file mode 100644 index 0000000..bec1ad5 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/repository/BookingRepository.java @@ -0,0 +1,20 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.Booking; +import comento.backend.ticket.dto.BookingResponse; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.*; + +@Repository +public interface BookingRepository extends JpaRepository { + /*해당 유저(u, ~@~)가 예약(b)한 공연 정보(p)와 좌석 정보(s) 조회*/ + @Query("select new comento.backend.ticket.dto.BookingResponse(p.title, p.startDate, u.email, s.seatType, s.seatNumber, p.price) " + + "from Seat s join s.performance p on s.performance = p.id " + + "join Booking b on b.seat = s.id " + + "join User u on u.id = b.user " + + "where s.isBooking = :isBooking and u.email = :email") + List findMyBooking(boolean isBooking, String email); +} diff --git a/ticket/src/main/java/comento/backend/ticket/repository/PerformanceRepository.java b/ticket/src/main/java/comento/backend/ticket/repository/PerformanceRepository.java new file mode 100644 index 0000000..1d6f872 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/repository/PerformanceRepository.java @@ -0,0 +1,16 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.Performance; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Date; +import java.util.List; +import java.util.Optional; + +@Repository +public interface PerformanceRepository extends JpaRepository { + List findByStartDateGreaterThanEqualOrderByStartDateAsc(Date startDate); + List findByTitleAndStartDateGreaterThanEqualOrderByStartDate(String title, Date startDate); + Optional findByIdAndTitle(Long id, String title); +} diff --git a/ticket/src/main/java/comento/backend/ticket/repository/SeatRepository.java b/ticket/src/main/java/comento/backend/ticket/repository/SeatRepository.java new file mode 100644 index 0000000..0112f4e --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/repository/SeatRepository.java @@ -0,0 +1,17 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import comento.backend.ticket.dto.SeatType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.*; + +@Repository +public interface SeatRepository extends JpaRepository { + List findByPerformance(Performance performance); + Optional findByPerformanceAndSeatTypeAndSeatNumber(Performance performance, SeatType seatType, Integer seatNumber); + //isBooking 여부 확인을 위해 사용 + Optional findByPerformanceAndSeatTypeAndSeatNumberAndIsBooking(Performance performance, SeatType seatType, Integer seatNumber, boolean isBooking); +} diff --git a/ticket/src/main/java/comento/backend/ticket/repository/UserRepository.java b/ticket/src/main/java/comento/backend/ticket/repository/UserRepository.java new file mode 100644 index 0000000..1706383 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/repository/UserRepository.java @@ -0,0 +1,13 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/ticket/src/main/java/comento/backend/ticket/service/BookingHistoryService.java b/ticket/src/main/java/comento/backend/ticket/service/BookingHistoryService.java new file mode 100644 index 0000000..a4d74d9 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/service/BookingHistoryService.java @@ -0,0 +1,20 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.domain.BookingHistory; +import comento.backend.ticket.dto.BookingHistoryDto; +import comento.backend.ticket.repository.BookingHistoryRepository; +import org.springframework.stereotype.Service; + +@Service +public class BookingHistoryService { + private final BookingHistoryRepository bookingHistoryRepository; + + public BookingHistoryService(BookingHistoryRepository bookingHistoryRepository) { + this.bookingHistoryRepository = bookingHistoryRepository; + } + + public BookingHistory saveBookingHistory(final BookingHistoryDto bookingHistoryDto){ + BookingHistory bookingHistory = bookingHistoryDto.toEntity(); + return bookingHistoryRepository.save(bookingHistory); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/service/BookingService.java b/ticket/src/main/java/comento/backend/ticket/service/BookingService.java new file mode 100644 index 0000000..4e8702c --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/service/BookingService.java @@ -0,0 +1,81 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.*; +import comento.backend.ticket.dto.*; +import comento.backend.ticket.repository.BookingRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class BookingService { + private final BookingRepository bookingRepository; + private final PerformanceService performanceService; + private final BookingHistoryService bookingHistoryService; + private final UserService userService; + private final SeatService seatService; + + public BookingService(BookingRepository bookingRepository, PerformanceService performanceService, + BookingHistoryService bookingHistoryService, UserService userService, + SeatService seatService) { + this.bookingRepository = bookingRepository; + this.performanceService = performanceService; + this.bookingHistoryService = bookingHistoryService; + this.userService = userService; + this.seatService = seatService; + } + + // @TODO : 동시성 문제 해결해야 함 + //예약 정보를 booking에 저장하고, Seat 정보에는 is_booking을 true로 변경 + @Transactional + public Booking saveBookging(final BookingDto reqBooking){ + final User user = userService.getUser(reqBooking.getEmail()); + final Performance performance = performanceService.getPerformance(reqBooking.getId(), reqBooking.getTitle()); + final Seat seat = seatService.getIsBooking(user, performance, reqBooking.getSeatType(), reqBooking.getSeatNumber(), false); //false라면 예약 가능 + + //seat 테이블의 is_booking 칼럼을 true로 update + updateSeat(seat, performance, reqBooking); + + //seat의 값이 있다면, booking 가능 + saveBookingSucessLog(user, performance, seat); + + //booking 여부 insert + Booking booking = reqBooking.toEntity(user, performance, seat); + return bookingRepository.save(booking); + } + + private void updateSeat(final Seat seat, final Performance performance, final BookingDto reqBooking) { + SeatDto seatDto = SeatDto.builder() + .seat_id(seat.getId()) + .performance(performance) + .seatType(reqBooking.getSeatType()) + .seatNumber(reqBooking.getSeatNumber()) + .isBooking(true) + .build(); + seatService.updateSeat(seatDto); + } + + private void saveBookingSucessLog(final User user, final Performance performance, final Seat seat) { + //booking의 성공 여부 history 저장 + BookingHistoryDto bookingHistoryDto = BookingHistoryDto.builder() + .user(user) + .performance(performance) + .seat(seat) + .isSuccess(true) + .build(); + bookingHistoryService.saveBookingHistory(bookingHistoryDto); + } + + @Transactional(readOnly = true) + public List getMyBooking(String email){ + User user = userService.getUser(email); + List result = bookingRepository.findMyBooking(true, user.getEmail()); + if(result.isEmpty()){ + throw new NotFoundDataException(); + } + return result; + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/service/PerformanceService.java b/ticket/src/main/java/comento/backend/ticket/service/PerformanceService.java new file mode 100644 index 0000000..5282968 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/service/PerformanceService.java @@ -0,0 +1,39 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.dto.PerformanceDto; +import comento.backend.ticket.dto.PerformanceResponse; +import comento.backend.ticket.repository.PerformanceRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Date; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class PerformanceService { + private final PerformanceRepository performanceRepository; + + public PerformanceService(PerformanceRepository performanceRepository) { + this.performanceRepository = performanceRepository; + } + + public List getListPerformance(final PerformanceDto performanceDto){ + Date startDate = performanceDto.getStartDate(); + String title = performanceDto.getTitle(); + List result = title != null ? + performanceRepository.findByTitleAndStartDateGreaterThanEqualOrderByStartDate(title, startDate) : + performanceRepository.findByStartDateGreaterThanEqualOrderByStartDateAsc(startDate); + if(result.isEmpty()){ + throw new NotFoundDataException(); + } + return result.stream().map(PerformanceResponse::of).collect(Collectors.toList()); + } + + public Performance getPerformance(final Long id, final String title){ + Optional performance = performanceRepository.findByIdAndTitle(id, title); + return performance.orElseThrow(NotFoundDataException::new); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/service/SeatService.java b/ticket/src/main/java/comento/backend/ticket/service/SeatService.java new file mode 100644 index 0000000..c50d32e --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/service/SeatService.java @@ -0,0 +1,67 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.config.customException.DuplicatedException; +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import comento.backend.ticket.domain.User; +import comento.backend.ticket.dto.*; +import comento.backend.ticket.repository.SeatRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class SeatService { + private final SeatRepository seatRepository; + private final BookingHistoryService bookingHistoryService; + + public SeatService(SeatRepository seatRepository, BookingHistoryService bookingHistoryService) { + this.seatRepository = seatRepository; + this.bookingHistoryService = bookingHistoryService; + } + + @Transactional(readOnly = true) + public List getListPerformanceSeat(final PerformanceResponse performanceData) { + Performance performance = new Performance(); + performance.setId(performanceData.getId()); + List seatResult = seatRepository.findByPerformance(performance); + if(seatResult.isEmpty()){ + throw new NotFoundDataException(); + } + return seatResult.stream().map(SeatResponse::of).collect(Collectors.toList()); + } + + public Seat updateSeat(final SeatDto seatDto) { + Seat seat = seatDto.toEntity(); + return seatRepository.save(seat); + } + + // @TODO : insert 쿼리도 출력되고, 예외도 발생하지만 BookingHisotry 테이블에 새 데이터 insert가 안된다 .. + @Transactional + public Seat getIsBooking(final User user, final Performance performance, final SeatType seatType, final Integer seatNumber, final boolean isBooking) { + Seat seatIsBooking = seatRepository.findByPerformanceAndSeatTypeAndSeatNumberAndIsBooking(performance, seatType, seatNumber, isBooking) + .orElseGet(()-> null); + if(Objects.isNull(seatIsBooking)){ + saveBookingFailLog(user, performance, null); + throw new DuplicatedException("SeatService"); + } + return seatIsBooking; + } + + private void saveBookingFailLog(final User user, final Performance performance, final Seat seat) { + //실패 여부 BookingHistory에 저장 + BookingHistoryDto bookingHistoryDto = BookingHistoryDto.builder() + .id(null) + .user(user) + .performance(performance) + .seat(seat) + .isSuccess(false) + .build(); + bookingHistoryService.saveBookingHistory(bookingHistoryDto); + } +} diff --git a/ticket/src/main/java/comento/backend/ticket/service/UserService.java b/ticket/src/main/java/comento/backend/ticket/service/UserService.java new file mode 100644 index 0000000..2890a98 --- /dev/null +++ b/ticket/src/main/java/comento/backend/ticket/service/UserService.java @@ -0,0 +1,41 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.config.customException.DuplicatedException; +import comento.backend.ticket.config.customException.NoAuthException; +import comento.backend.ticket.domain.User; +import comento.backend.ticket.dto.UserDto; +import comento.backend.ticket.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.util.Objects; +import java.util.Optional; + + +@Service +public class UserService { + private final UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public User saveUser(final UserDto userDto){ + User user = userDto.toEntity(); + + if(!checkEmailDuplicate(user.getEmail())) { + throw new DuplicatedException("UserService"); + } + + return userRepository.save(user); + } + + private boolean checkEmailDuplicate(final String email){ + User user = userRepository.findByEmail(email).orElseGet(()->null); + return Objects.isNull(user); //null이면 true + } + + public User getUser(final String email){ + Optional user = userRepository.findByEmail(email); + return user.orElseThrow(NoAuthException::new); + } +} diff --git a/ticket/src/main/resources/logback.xml b/ticket/src/main/resources/logback.xml new file mode 100644 index 0000000..b361d71 --- /dev/null +++ b/ticket/src/main/resources/logback.xml @@ -0,0 +1,29 @@ + + + + + + + + + logs/ticket-reservation-server.log + + + logs/ticket-reservation-server-%d{yyyy-MM-dd}.log + + 30 + + + ${LOG_PATTERN} + + + + + + + + + + + + \ No newline at end of file diff --git a/ticket/src/test/java/comento/backend/ticket/repository/PerformanceRepositroyTest.java b/ticket/src/test/java/comento/backend/ticket/repository/PerformanceRepositroyTest.java new file mode 100644 index 0000000..48184ac --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/repository/PerformanceRepositroyTest.java @@ -0,0 +1,89 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.Performance; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +public class PerformanceRepositroyTest { + private final PerformanceRepository performanceRepository; + + @Autowired + public PerformanceRepositroyTest(PerformanceRepository performanceRepository) { + this.performanceRepository = performanceRepository; + } + + //Junit5 부터 접근제어자 생략 가능 (JUnit4 까지는 public이어야 했음!) + + @Test + @DisplayName("[성공] 날짜를 정확하게 입력한 경우") + void 공연_날짜_정보_조회() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2020-06-20"); + + List result = performanceRepository.findByStartDateGreaterThanEqualOrderByStartDateAsc(startDate); + + assertThat(result.size()).isNotZero(); + } + + @Test + @DisplayName("[성공] 날짜, 공연제목을 정확하게 입력한 경우") + void 공연_날짜이름_정보_조회() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2020-06-20"); + String title = "국립무용단 <산조>"; + + List result = performanceRepository.findByTitleAndStartDateGreaterThanEqualOrderByStartDate(title, startDate); + + assertThat(result.size()).isNotZero(); + } + + @Test + @DisplayName("[실패] NOT FOUND ERROR") + void 공연_날짜_정보_조회실패() throws ParseException, NotFoundDataException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-31"); + + List result = performanceRepository.findByStartDateGreaterThanEqualOrderByStartDateAsc(startDate); + + assertThat(result).isNull(); + + /*앙댐 + //given + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-31"); + + //when + final NotFoundDataException exception = assertThrows(NotFoundDataException.class, () -> { + performanceRepository.findByStartDateGreaterThanEqualOrderByStartDateAsc(startDate); + }); + + //then + assertThat(exception.getMessage()).isNotNull(); + */ + } + + @Test + @DisplayName("[실패] BAD REQUEST") + void 공연_날짜이름_정보_조회실패() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("ㄴ"); + String title = "국립무용단 <산조>"; + + List result = performanceRepository.findByTitleAndStartDateGreaterThanEqualOrderByStartDate(title, startDate); + + assertThat(result).isNull(); + } +} diff --git a/ticket/src/test/java/comento/backend/ticket/repository/SeatRepositoryTest.java b/ticket/src/test/java/comento/backend/ticket/repository/SeatRepositoryTest.java new file mode 100644 index 0000000..611e9af --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/repository/SeatRepositoryTest.java @@ -0,0 +1,42 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import comento.backend.ticket.dto.SeatType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +public class SeatRepositoryTest { + private final SeatRepository seatRepository; + + @Autowired + public SeatRepositoryTest(SeatRepository seatRepository) { + this.seatRepository = seatRepository; + } + + @Test + @DisplayName("조회를 성공한다면 ID값을 가져온다.") + void seat_정보_조회() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date date = format.parse("2021-06-20"); + Performance performance = new Performance(1L, "2021 정오의 음악회 5월", "국악", date, date, + "전석 20,000원", "국립극장\n하늘극장\n8세 이상 관람가", "70분"); + + Optional result = seatRepository.findByPerformanceAndSeatTypeAndSeatNumber(performance, SeatType.VIP, 2); + + assertThat(result.get().getId()).isNotZero(); + } +} diff --git a/ticket/src/test/java/comento/backend/ticket/repository/UserRepositryTest.java b/ticket/src/test/java/comento/backend/ticket/repository/UserRepositryTest.java new file mode 100644 index 0000000..9086af1 --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/repository/UserRepositryTest.java @@ -0,0 +1,53 @@ +package comento.backend.ticket.repository; + +import comento.backend.ticket.domain.User; +import comento.backend.ticket.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import static org.assertj.core.api.Assertions.assertThat; + +//given +//when +//then + +@SpringBootTest +@Transactional +public class UserRepositryTest { + private final UserRepository userRepository; + + @Autowired + public UserRepositryTest(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Test + @DisplayName("이메일을 입력하면 사용자 등록에 성공하여 DB에 저장한다.") + public void 사용자_등록_성공(){ + //given + User user = new User(); + user.setEmail("kimhyejung12@naver.com"); + + //when + User result = userRepository.save(user); + + //then + assertThat(result.getId()).isNotNull(); + } + + @Test + @DisplayName("이메일을 입력하면 사용자 등록에 성공하여 DB에 저장한다.") + public void 사용자_등록_중복으로_인한_실패(){ + //given + User user = new User(); + user.setEmail("kimhyejung12@naver.com"); //error 409 + + //when + User result = userRepository.save(user); + + //then + assertThat(result.getId()).isNotNull(); + } +} diff --git a/ticket/src/test/java/comento/backend/ticket/service/MockPerformanceServiceTest.java b/ticket/src/test/java/comento/backend/ticket/service/MockPerformanceServiceTest.java new file mode 100644 index 0000000..1e69149 --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/service/MockPerformanceServiceTest.java @@ -0,0 +1,129 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.dto.PerformanceDto; +import comento.backend.ticket.dto.PerformanceResponse; +import comento.backend.ticket.repository.PerformanceRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.transaction.annotation.Transactional; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; //몇 번 호출되었는지 검증 +import static org.mockito.Mockito.verify; //해당 기능이 사용되었는지 검증 + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +@ExtendWith(MockitoExtension.class) //JUnit5와 Mockito 연동 +public class MockPerformanceServiceTest { + + @Mock //mock객체로 생성 + private PerformanceRepository performanceRepository; + + private PerformanceService performanceService; + + @BeforeEach + void init() { + //실제 존재하는 performanceRepository가 주입되는것이 아닌 Mock PerformanceRepository가 주입 + performanceService = new PerformanceService(performanceRepository); + } + @Test + @DisplayName("[실패] 없는 날짜 정보 입력 - 404 NOT FOUND ERROR") + void 공연정보가_없으면_예외를_던진다2() throws ParseException { + //given + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-31"); + + //performanceRepository를 mock했으니 테스트 상황에서 원하는 결과를 새롭게 구현하는 코드. + //테스트가 돌아가는 동안에는 willReturn에서 반환하는 결과를 반드시 return하므로 PerformanceRepository를 의존하지 않고도 돌아가는 테스트코드가 완성 + given(performanceRepository.findByStartDateGreaterThanEqualOrderByStartDateAsc(startDate)) + .willReturn(new ArrayList()); //빈 배열 반환 + //when + //공연정보가 없는 경우 Service에서는 NOT FOUND ERROR 예외 호출 + NotFoundDataException nfde = assertThrows(NotFoundDataException.class, () -> { + performanceService.getListPerformance(new PerformanceDto(null, startDate)); + }); + //then + String msg = nfde.getMessage(); + assertThat(msg).isNull(); + } + @Test + @DisplayName("[성공] 날짜를 정확히 입력한 경우") + void 공연정보가_있으면_응답한다1() throws ParseException { + //given + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-20"); + + given(performanceRepository.findByStartDateGreaterThanEqualOrderByStartDateAsc(startDate)) + .willReturn(PerformanceFixture.performances(startDate)); + //when + List result = performanceService.getListPerformance(new PerformanceDto(null, startDate)); + + //then + assertThat(result.size()).isNotZero(); + } + @Test + @DisplayName("[성공] 날짜, 제목을 정확히 입력한 경우") + void 공연정보가_있으면_응답한다2() throws ParseException { + //given + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-20"); + String title = "국립무용단 <산조>"; + PerformanceDto dto = new PerformanceDto(title, startDate); + given(performanceRepository.findByTitleAndStartDateGreaterThanEqualOrderByStartDate(title, startDate)) + .willReturn(PerformanceFixture.performances(startDate, title)); + //when + List result = performanceService.getListPerformance(dto); + + //then + assertThat(result.size()).isNotZero(); + } + private static class PerformanceFixture { + public static List performances(final Date date) { + List performances = new ArrayList<>(); + performances.add(performance(date)); + return performances; + } + public static List performances(final Date date, final String title) { + List performances = new ArrayList<>(); + performances.add(performance(date, title)); + return performances; + } + public static Performance performance(final Date startDate) { + return Performance.builder() + .id(null) + .title(null) + .startDate(startDate) + .endDate(null) + .genere(null) + .description(null) + .price(null) + .runningTime(null) + .build(); + } + public static Performance performance(final Date startDate, final String title) { + return Performance.builder() + .id(null) + .title(title) + .startDate(startDate) + .endDate(null) + .genere(null) + .description(null) + .price(null) + .runningTime(null) + .build(); + } + } +} \ No newline at end of file diff --git a/ticket/src/test/java/comento/backend/ticket/service/MockSeatServiceTest.java b/ticket/src/test/java/comento/backend/ticket/service/MockSeatServiceTest.java new file mode 100644 index 0000000..c8659ac --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/service/MockSeatServiceTest.java @@ -0,0 +1,95 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.config.customException.NotFoundDataException; +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.domain.Seat; +import comento.backend.ticket.dto.PerformanceResponse; +import comento.backend.ticket.dto.SeatResponse; +import comento.backend.ticket.dto.SeatType; +import comento.backend.ticket.repository.SeatRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.transaction.annotation.Transactional; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +@ExtendWith(MockitoExtension.class) +public class MockSeatServiceTest { + @Mock //mock객체로 생성 + private SeatRepository seatRepository; + + private SeatService seatService; + private BookingHistoryService bookingHistoryService; + + @BeforeEach + void init() { + seatService = new SeatService(seatRepository, bookingHistoryService); //mock 주입 + } + @Test + @DisplayName("[실패] 404 NOT FOUND ERROR") + void 좌석_정보가_없으면_예외를_던진다() { + //given + Performance performance = new Performance(1L, null, null, null, null, null, null, null); + PerformanceResponse performanceResponse = new PerformanceResponse(performance.getId(), "2022 정오의 음악회 5월", null, null, + "전석 20,000원", null, null, null); + + given(seatRepository.findByPerformance(performance)) + .willReturn(new ArrayList<>()); + //when + NotFoundDataException nfde = assertThrows(NotFoundDataException.class, () -> { + seatService.getListPerformanceSeat(performanceResponse); + }); + //then + String msg = nfde.getMessage(); + assertThat(msg).isEqualTo(null); + } + @Test + @DisplayName("[성공]") + void 좌석_정보를_보여준다() throws ParseException { + //given + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date date = format.parse("2021-06-20"); + PerformanceResponse performanceResponse = new PerformanceResponse(1L, "2021 정오의 음악회 5월", date, date, + "전석 20,000원", "국악", "국립극장\n하늘극장\n8세 이상 관람가", "70분"); + + Performance performance = new Performance(); + performance.setId(performanceResponse.getId()); + given(seatRepository.findByPerformance(performance)) + .willReturn(SeatFixture.seats()); + //when + List list = seatService.getListPerformanceSeat(performanceResponse); + + //then + assertThat(list.size()).isNotZero(); + } + + //반복적으로 테스트하여 사용할 객체를 위한 클래스 생성 + private static class SeatFixture { + public static List seats() { + List seats = new ArrayList<>(); + seats.add(seat()); + return seats; + } + public static Seat seat() { + return Seat.builder() + .id(1L) + .seatType(SeatType.S) + .seatNumber(1) + .performance(new Performance()) + .build(); + } + } +} diff --git a/ticket/src/test/java/comento/backend/ticket/service/PerformanceServiceTest.java b/ticket/src/test/java/comento/backend/ticket/service/PerformanceServiceTest.java new file mode 100644 index 0000000..178fc14 --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/service/PerformanceServiceTest.java @@ -0,0 +1,82 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.domain.Performance; +import comento.backend.ticket.dto.PerformanceDto; +import comento.backend.ticket.dto.PerformanceResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest //통합테스트 +@Transactional +public class PerformanceServiceTest { + private final PerformanceService performanceService; + + @Autowired + public PerformanceServiceTest(PerformanceService performanceService) { + this.performanceService = performanceService; + } + + @Test + @DisplayName("[성공] 날짜를 정확하게 입력한 경우") + void 공연_날짜_정보_조회() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-20"); + + PerformanceDto dto = new PerformanceDto(null, startDate); + + List result = performanceService.getListPerformance(dto); + + assertThat(result.size()).isNotZero(); + } + + @Test + @DisplayName("[성공] 날짜, 공연제목을 정확하게 입력한 경우") + void 공연_날짜이름_정보_조회() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-20"); + String title = "국립무용단 <산조>"; + + PerformanceDto dto = new PerformanceDto(title, startDate); + + List result = performanceService.getListPerformance(dto); + + assertThat(result.size()).isNotZero(); + } + + @Test + @DisplayName("[실패] NOT FOUND ERROR") + void 공연_날짜_정보_조회실패() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("2021-06-31"); + String title = "국립무용단 <산조>"; + + PerformanceDto dto = new PerformanceDto(title, startDate); + + List result = performanceService.getListPerformance(dto); + assertThat(result).isNull(); + } + + @Test + @DisplayName("[실패] BAD REQUEST") + void 공연_날짜이름_정보_조회실패() throws ParseException { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date startDate = format.parse("S"); + String title = "국립무용단 <산조>"; + + PerformanceDto dto = new PerformanceDto(title, startDate); + + List result = performanceService.getListPerformance(dto); + assertThat(result).isNull(); + } +} diff --git a/ticket/src/test/java/comento/backend/ticket/service/UserServiceTest.java b/ticket/src/test/java/comento/backend/ticket/service/UserServiceTest.java new file mode 100644 index 0000000..ece10ad --- /dev/null +++ b/ticket/src/test/java/comento/backend/ticket/service/UserServiceTest.java @@ -0,0 +1,81 @@ +package comento.backend.ticket.service; + +import comento.backend.ticket.domain.User; +import comento.backend.ticket.dto.UserDto; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +//given +//when +//then + +@SpringBootTest +@Transactional +public class UserServiceTest { + private final UserService userService; + + @Autowired + public UserServiceTest(UserService userService) { + this.userService = userService; + } + + @Test + @DisplayName("이메일을 입력하면 사용자 등록에 성공하여 DB에 저장한다.") + public void 이메일_등록_성공(){ + //given + UserDto userDto = new UserDto("kimhyejung12@naver.com"); + + //when + Optional result = Optional.ofNullable(userService.saveUser(userDto)); + + //then + result.ifPresent(selectUser -> { + System.out.println(selectUser.getId()); + System.out.println(selectUser.getEmail()); + System.out.println(selectUser.getCreateAt()); + }); + } + + @Test + @DisplayName("이미 존재하는 이메일을 입력하면 사용자 등록에 실패하여 예외를 반환한다.") + public void 이메일_등록_중복으로_인한_실패(){ + //given + UserDto userDto = new UserDto("kimhyejung12@naver.com"); + UserDto userDto2 = new UserDto("kimhyejung12@naver.com"); + + //when + Optional result = Optional.ofNullable(userService.saveUser(userDto)); + Optional result2 = Optional.ofNullable(userService.saveUser(userDto2)); //error 409 + + //then + result.ifPresent(selectUser -> { + System.out.println(selectUser.getId()); + System.out.println(selectUser.getEmail()); + System.out.println(selectUser.getCreateAt()); + }); + } + + @Test + @DisplayName("이메일 형태가 아닌 경우 or 타입 오류 발생 시 사용자 등록에 실패하여 예외가 반환한다.") + public void 이메일_등록_타입오류로_인한_실패(){ //@TODO : 에러가 발생해야 정상인데 성공 ... (Postman에서는 정상적으로 에러 발생) + //given + String email = "12"; //success? + UserDto userDto = new UserDto(email); + + //when + Optional result = Optional.ofNullable(userService.saveUser(userDto)); //error 400 + + //then + result.ifPresent(selectUser -> { + System.out.println(selectUser.getId()); + System.out.println(selectUser.getEmail()); + System.out.println(selectUser.getCreateAt()); + }); + } + +}