Skip to content

Commit

Permalink
Merge branch 'main' into member/196
Browse files Browse the repository at this point in the history
  • Loading branch information
eunbc committed Jan 10, 2024
2 parents a30cb5f + 6088ce9 commit bf298c3
Show file tree
Hide file tree
Showing 16 changed files with 400 additions and 92 deletions.
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,30 @@
[ERD](https://www.erdcloud.com/d/ZadArGCaQXFcxZuu8)
### 모듈 구조
#### api
- api-member
- api-event
- api-payment
- api-member
회원 도메인
- api-event
공연 도메인
- api-booking
결제 도메인
#### batch
스프링 배치 모듈
#### core
- core-domain
- core-infra
- core-domain
JPA 엔티티, 리포지토리
- core-infra
queryDsl, RDB 설정 파일
- core-infra-es
elastic search 설정 파일, document, searchRepository
- core-security
spring security 설정 파일
## 실행 방법
1. git clone
2. RDB, 레디스 실행 ```docker-compose up -d```
3. api-event 모듈로 이동 ```cd /api/api-event```
4. 엘라스틱 서치 도커 이미지 빌드 ```docker build -t el:0.1 -f ./Dockerfile .```
5. ELK 스택 실행 ```docker-compose up -d```
6. api-booking, api-event, api-member 각 모듈에서 스프링 어플리케이션 실행
## 테스트 방법
- 통합 http 테스트는 /http/bingterpark.http에 있습니다.
- 어드민 플로우, 유저 플로우 http 코드를 위에서부터 하나씩 실행하시면 됩니다.
32 changes: 26 additions & 6 deletions api/api-booking/http/booking.http
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhb
POST http://localhost:8082/api/v1/bookings/enter-queue
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}
Booking-Session-Id: {sessionId}

{
"eventId": 1
Expand All @@ -15,14 +15,14 @@ Booking-Session-Id: {세션아이디}
### 대기열 조회
GET http://localhost:8082/api/v1/bookings/order-in-queue?eventId=1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}
Booking-Session-Id: {sessionId}


### 예매 토큰 발급
POST http://localhost:8082/api/v1/bookings/issue-token
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}
Booking-Session-Id: {sessionId}

{
"eventId": 1
Expand All @@ -32,7 +32,7 @@ Booking-Session-Id: {세션아이디}
POST http://localhost:8082/api/v1/bookings/exit-queue
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Session-Id: {세션아이디}
Booking-Session-Id: {sessionId}

{
"eventId": 1
Expand All @@ -43,16 +43,36 @@ GET http://localhost:8082/api/v1/seats?eventTimeId=1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB

### 좌석 선택
POST http://localhost:8082/api/v1/seats/1/select
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB

{
"seatId": 1
}

### 좌석 선택 해제
POST http://localhost:8082/api/v1/seats/1/deselect
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB

{
"seatId": 1
}

### 예매 ~ 결제 (브라우저에서 진행해 주세요)
GET http://localhost:8082/bookings/create
GET http://localhost:8082/bookings

### [Optional] 예매 이탈
POST http://localhost:8082/api/v1/bookings/bookingCreateTestId/exit
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB

### 예매 취소 (브라우저에서 생성된 예매번호로 진행해 주세요)
POST http://localhost:8082/api/v1/bookings/1704704631741/cancel
POST http://localhost:8082/api/v1/bookings/{bookingId}/cancel
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ
Content-Type: application/json

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public List<AreaResponse> getSeats(SeatsGetRequest request) {

public void selectSeat(Long seatId, Long memberId) {
if (seatLockService.isSeatLocked(seatId)) {
Optional<Long> selectorIdOpt = seatLockService.getSelectorId(seatId);

if (selectorIdOpt.isPresent() && selectorIdOpt.get().equals(memberId)) {
return;
}

throw new BookingException(BookingErrorCode.SEAT_BEING_BOOKED);
}

Expand All @@ -56,11 +62,11 @@ public void selectSeat(Long seatId, Long memberId) {
public void deselectSeat(Long seatId, Long memberId) {
Optional<Long> selectorIdOpt = seatLockService.getSelectorId(seatId);

if(selectorIdOpt.isEmpty()) {
if (selectorIdOpt.isEmpty()) {
updateSeatStatusToAvailable(seatId);
throw new BookingException(BookingErrorCode.SEAT_SELECTION_EXPIRED);
return;
}

if (!selectorIdOpt.get().equals(memberId)) {
throw new BookingException(BookingErrorCode.SEAT_SELECTED_BY_ANOTHER_MEMBER);
}
Expand Down
2 changes: 1 addition & 1 deletion api/api-booking/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ spring:
payment:
toss:
test-client-api-key: test_ck_Gv6LjeKD8aE1pdWDXXNw8wYxAdXy
test-secret-api-key: test # 노출되면 안됨!! 임시값 넣어놓음
test-secret-api-key: test_sk_eqRGgYO1r5MNel9067jarQnN2Eya
success-url: https://localhost:8080/api/v1/success
fail-url: https://localhost:8080/api/v1/fail

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ <h2>주문 정보 입력</h2>

$.ajax({
type: "POST",
url: "/api/v1/bookings", // 실제 서버 엔드포인트에 맞게 수정
url: "/api/v1/bookings/create", // 실제 서버 엔드포인트에 맞게 수정
contentType: "application/json",
headers: {
// "Authorization": "Bearer " + "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ",
"Authorization": "Bearer " + "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ",
"Booking-Authorization": "Bearer " + "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB"
},
data: JSON.stringify(orderData),
Expand Down
2 changes: 1 addition & 1 deletion api/api-event/http/event-search.http
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### 공연 키워드 검색
GET http://localhost:8080/api/v1/events/search/keyword?page=1&size=10&keyword=드라큘라&startedAt=2023-01-01T12:00:00
GET http://localhost:8080/api/v1/events/search/keyword?page=1&size=10&keyword=&startedAt=2023-01-01T12:00:00

### 실시간 인기 검색어
GET http://localhost:8080/api/v1/events/search/top-ten
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
package com.pgms.apievent.event.repository;

import static com.pgms.coredomain.domain.booking.QBooking.*;
import static com.pgms.coredomain.domain.event.QEvent.*;
import static com.pgms.coredomain.domain.event.QEventReview.*;
import static com.pgms.coredomain.domain.event.QEventTime.*;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;

import com.pgms.apievent.event.dto.request.EventPageRequest;
import com.pgms.apievent.event.dto.response.EventResponse;
import com.pgms.coredomain.domain.event.GenreType;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

import static com.pgms.coredomain.domain.booking.QBooking.booking;
import static com.pgms.coredomain.domain.event.QEvent.event;
import static com.pgms.coredomain.domain.event.QEventReview.eventReview;
import static com.pgms.coredomain.domain.event.QEventTime.eventTime;

@Repository
@RequiredArgsConstructor
Expand Down Expand Up @@ -55,7 +53,7 @@ public Page<EventResponse> getEventsPageByGenreSortedByRanking(EventPageRequest

JPAQuery<Long> countQuery = jpaQueryFactory.select(event.count())
.from(event)
.join(eventTime).fetchJoin()
.leftJoin(eventTime).fetchJoin()
.on(eventTime.event.eq(event))
.leftJoin(booking).fetchJoin()
.on(eventTime.id.eq(booking.time.id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
import com.pgms.apievent.eventSearch.dto.request.EventKeywordSearchRequest;
import com.pgms.apievent.eventSearch.dto.response.RecentTop10KeywordsResponse;
import com.pgms.apievent.eventSearch.service.EventSearchService;
import com.pgms.apievent.exception.EventException;
import com.pgms.coredomain.response.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

import static com.pgms.apievent.exception.EventErrorCode.BINDING_FAILED_EXCEPTION;

@RestController
@RequestMapping("/api/v1/events/search")
@RequiredArgsConstructor
Expand All @@ -24,7 +28,12 @@ public class EventSearchController {

@GetMapping("/keyword")
public ResponseEntity<ApiResponse> searchEventsByKeyword(
@ModelAttribute @Valid EventKeywordSearchRequest eventKeywordSearchRequest) {
@ModelAttribute @Valid EventKeywordSearchRequest eventKeywordSearchRequest,
BindingResult bindingResult) {
if(bindingResult.hasErrors()){
throw new EventException(BINDING_FAILED_EXCEPTION);
}

PageResponseDto response = eventSearchService.searchEventsByKeyword(eventKeywordSearchRequest);
return ResponseEntity.ok(ApiResponse.ok(response));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public enum EventErrorCode implements BaseErrorCode {
REVIEWER_MISMATCH_EXCEPTION("REVIEWER MISMATCH", HttpStatus.BAD_REQUEST, "리뷰 작성자가 일치하지 않습니다."),
EVENT_REVIEW_NOT_FOUND("EVENT REVIEW NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 공연 리뷰입니다."),
UNSUPPORTED_FILE_EXTENSION("UNSUPPORTED FILE EXTENSION", HttpStatus.BAD_REQUEST, "지원되지 않는 파일 확장자입니다."),
S3_UPLOAD_FAILED_EXCEPTION("S3 UPLOAD FAILED", HttpStatus.INTERNAL_SERVER_ERROR, "S3에 파일 업로드를 실패했습니다.");
S3_UPLOAD_FAILED_EXCEPTION("S3 UPLOAD FAILED", HttpStatus.INTERNAL_SERVER_ERROR, "S3에 파일 업로드를 실패했습니다."),
BINDING_FAILED_EXCEPTION("BINDING FAILED", HttpStatus.BAD_REQUEST, "ModelAttribute 필드 바인딩이 실패했습니다.");

private final String errorCode;
private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.pgms.apievent.exception;

import static com.pgms.apievent.exception.EventErrorCode.*;

import java.util.List;
import java.util.Objects;

import com.pgms.coredomain.domain.common.BaseErrorCode;
import com.pgms.coredomain.response.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
Expand All @@ -16,20 +14,15 @@
import com.pgms.coredomain.domain.common.BaseErrorCode;
import com.pgms.coredomain.response.ErrorResponse;
import com.pgms.coresecurity.security.exception.SecurityCustomException;
import java.util.List;
import java.util.Objects;

import lombok.extern.slf4j.Slf4j;
import static com.pgms.apievent.exception.EventErrorCode.VALIDATION_FAILED;

@Slf4j
@RestControllerAdvice
public class EventGlobalExceptionHandler {

@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
log.error(">>>>> Internal Server Error : {}", ex);
ErrorResponse errorResponse = new ErrorResponse("INTERNAL SERVER ERROR", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}

@ExceptionHandler(EventException.class)
protected ResponseEntity<ErrorResponse> handleEventCustomException(EventException ex) {
log.warn(">>>>> Custom Exception : {}", ex);
Expand All @@ -55,5 +48,11 @@ protected ResponseEntity<ErrorResponse> handleSecurityCustomException(SecurityCu
log.warn(">>>>> SecurityCustomException : {}", ex);
BaseErrorCode errorCode = ex.getErrorCode();
return ResponseEntity.status(errorCode.getStatus()).body(errorCode.getErrorResponse());

@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
log.error(">>>>> Internal Server Error : {}", ex);
ErrorResponse errorResponse = new ErrorResponse("INTERNAL SERVER ERROR", ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public boolean isCancelable() {
return this.cancel == null
&& !this.time.getEvent().isStarted()
&& this.payment.isCancelable()
&& this.status == BookingStatus.WAITING_FOR_PAYMENT || this.status == BookingStatus.PAYMENT_COMPLETED;
&& (this.status == BookingStatus.WAITING_FOR_PAYMENT || this.status == BookingStatus.PAYMENT_COMPLETED);
}

public boolean isPaid() {
Expand Down
Loading

0 comments on commit bf298c3

Please sign in to comment.