From ddb42b1cb9b9eca32a144a3032580322f2fee752 Mon Sep 17 00:00:00 2001 From: zerozae <84398970+park0jae@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:58:21 +0900 Subject: [PATCH 01/10] =?UTF-8?q?refactor:=20=EA=B3=B5=EC=97=B0=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: #169 EventErrorCode -> BaseErrorCode Implements & 예외 처리 코드 리팩토링 * refactor: #169 계층간 DTO 분리를 위해 검색관련 DTO 객체 추가 처리 * refactor: #169 전반적인 코드 포맷팅 리팩토링 * feat: #169 Enum 클래스 예외처리 로직 추가 --- .../event/dto/request/EventCreateRequest.java | 4 +- .../event/dto/request/EventUpdateRequest.java | 8 +- .../apievent/event/service/EventService.java | 8 +- .../dto/request/EventHallCreateRequest.java | 14 +- .../request/EventHallSeatCreateRequest.java | 6 +- .../dto/request/EventHallUpdateRequest.java | 14 +- .../dto/response/EventHallResponse.java | 28 +-- .../dto/response/EventHallSeatResponse.java | 14 +- .../eventHall/service/EventHallService.java | 140 ++++++++------- .../controller/EventSearchController.java | 18 +- .../request/EventKeywordSearchRequest.java | 14 +- .../response/RecentTop10KeywordsResponse.java | 5 + .../service/EventSearchService.java | 23 +-- .../dto/request/EventSeatsCreateRequest.java | 11 +- .../dto/response/EventSeatResponse.java | 18 +- .../dto/response/LeftEventSeatResponse.java | 3 +- .../eventSeat/service/EventSeatService.java | 165 +++++++++--------- .../controller/EventSeatAreaController.java | 6 +- .../request/EventSeatAreaCreateRequest.java | 9 +- .../request/EventSeatAreaUpdateRequest.java | 3 +- .../service/EventSeatAreaService.java | 12 +- .../controller/EventReviewController.java | 1 + .../dto/request/EventReviewCreateRequest.java | 4 +- .../dto/response/EventReviewResponse.java | 4 +- .../service/EventReviewService.java | 20 ++- .../dto/request/EventTimeCreateRequest.java | 10 +- .../dto/request/EventTimeUpdateRequest.java | 10 +- .../dto/response/EventTimeResponse.java | 4 +- .../eventtime/service/EventTimeService.java | 12 +- .../apievent/exception/CustomException.java | 14 -- .../apievent/exception/EventErrorCode.java | 10 +- .../apievent/exception/EventException.java | 16 ++ .../EventGlobalExceptionHandler.java | 22 +-- .../exception/EventHallNotFoundException.java | 9 - .../EventSeatAreaNotFoundException.java | 7 - .../image/service/EventImageService.java | 4 +- .../apievent/image/service/S3Service.java | 4 +- .../scheduler/EventDocumentScheduler.java | 60 ++++--- .../com/pgms/apievent/util/ImageUtil.java | 6 +- .../service/EventSeatServiceTest.java | 42 +++-- .../service/EventTimeServiceTest.java | 4 +- .../domain/event/EventSeatStatus.java | 16 +- .../coredomain/domain/event/GenreType.java | 4 +- .../coredomain/domain/event/SeatAreaType.java | 14 +- .../coreinfraes/buffer/DocumentBuffer.java | 24 +-- .../config/ElasticSearchConfig.java | 12 +- .../document/AccessLogDocument.java | 27 +-- .../coreinfraes/document/EventDocument.java | 23 ++- .../dto}/EventDocumentResponse.java | 2 +- .../dto/EventKeywordSearchDto.java | 5 +- .../EventSearchQueryRepository.java | 146 +++++++++------- .../repository/EventSearchRepository.java | 3 +- 52 files changed, 585 insertions(+), 477 deletions(-) delete mode 100644 api/api-event/src/main/java/com/pgms/apievent/exception/CustomException.java create mode 100644 api/api-event/src/main/java/com/pgms/apievent/exception/EventException.java delete mode 100644 api/api-event/src/main/java/com/pgms/apievent/exception/EventHallNotFoundException.java delete mode 100644 api/api-event/src/main/java/com/pgms/apievent/exception/EventSeatAreaNotFoundException.java rename {api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response => core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto}/EventDocumentResponse.java (93%) diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java index ba93b1e9..233682ed 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java @@ -34,8 +34,8 @@ public record EventCreateRequest( GenreType genreType, @NotNull(message = "이벤트 홀 ID는 필수 입력값 입니다.") - Long eventHallId -) { + Long eventHallId) { + public Event toEntity(EventHall eventHall) { return Event.builder() .title(title) diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java index cc4debd2..a4423377 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java @@ -1,14 +1,15 @@ package com.pgms.apievent.event.dto.request; +import java.time.LocalDateTime; + import com.pgms.coredomain.domain.event.GenreType; + import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; -import java.time.LocalDateTime; - public record EventUpdateRequest( @@ -39,6 +40,5 @@ LocalDateTime bookingEndedAt, @NotNull(message = "이벤트 홀 ID는 필수 입력값 입니다.") - Long eventHallId -) { + Long eventHallId) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/service/EventService.java b/api/api-event/src/main/java/com/pgms/apievent/event/service/EventService.java index bbbee625..ffdf35da 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/service/EventService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/service/EventService.java @@ -12,7 +12,7 @@ import com.pgms.apievent.event.dto.request.EventUpdateRequest; import com.pgms.apievent.event.dto.response.EventResponse; import com.pgms.apievent.event.repository.EventCustomRepository; -import com.pgms.apievent.exception.CustomException; +import com.pgms.apievent.exception.EventException; import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventEdit; import com.pgms.coredomain.domain.event.EventHall; @@ -86,7 +86,7 @@ public void deleteEventById(Long id) { private void validateDuplicateEvent(String title) { if (Boolean.TRUE.equals((eventRepository.existsEventByTitle(title)))) { - throw new CustomException(ALREADY_EXIST_EVENT); + throw new EventException(ALREADY_EXIST_EVENT); } } @@ -109,11 +109,11 @@ private EventEdit getEventEdit(EventUpdateRequest request) { private Event getEvent(Long eventId) { return eventRepository.findById(eventId) - .orElseThrow(() -> new CustomException(EVENT_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); } private EventHall getEventHall(Long eventHallId) { return eventHallRepository.findById(eventHallId) - .orElseThrow(() -> new CustomException(EVENT_HALL_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_HALL_NOT_FOUND)); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java index d8399623..2cddf787 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java @@ -1,12 +1,14 @@ package com.pgms.apievent.eventHall.dto.request; +import java.util.List; + import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import java.util.List; - -public record EventHallCreateRequest(@NotBlank(message = "공연장 이름은 빈칸을 지정할 수 없습니다.") - @Size(max = 25, message = "공연장 이름은 10자 이내로 입력해주세요.") String name, - String address, - List eventHallSeatCreateRequests) { +public record EventHallCreateRequest( + @NotBlank(message = "공연장 이름은 빈칸을 지정할 수 없습니다.") + @Size(max = 25, message = "공연장 이름은 10자 이내로 입력해주세요.") + String name, + String address, + List eventHallSeatCreateRequests) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallSeatCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallSeatCreateRequest.java index beeaf6e0..d77474ab 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallSeatCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallSeatCreateRequest.java @@ -3,6 +3,8 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record EventHallSeatCreateRequest(@NotBlank(message = "공연장 좌석은 빈칸을 지정할 수 없습니다.") - @Size(max = 10, message = "공연장 좌석은 10자 이내로 입력해주세요.") String name) { +public record EventHallSeatCreateRequest( + @NotBlank(message = "공연장 좌석은 빈칸을 지정할 수 없습니다.") + @Size(max = 10, message = "공연장 좌석은 10자 이내로 입력해주세요.") + String name) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java index d0a2fd52..2cd9680b 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java @@ -1,12 +1,14 @@ package com.pgms.apievent.eventHall.dto.request; +import java.util.List; + import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -import java.util.List; - -public record EventHallUpdateRequest(@NotBlank(message = "공연장 이름은 빈칸을 지정할 수 없습니다.") - @Size(max = 25, message = "공연장 이름은 10자 이내로 입력해주세요.") String name, - String address, - List eventHallSeatCreateRequests) { +public record EventHallUpdateRequest( + @NotBlank(message = "공연장 이름은 빈칸을 지정할 수 없습니다.") + @Size(max = 25, message = "공연장 이름은 10자 이내로 입력해주세요.") + String name, + String address, + List eventHallSeatCreateRequests) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallResponse.java index 7c80d8e6..d2ee9f6c 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallResponse.java @@ -1,18 +1,24 @@ package com.pgms.apievent.eventHall.dto.response; +import java.util.List; + import com.pgms.coredomain.domain.event.EventHall; -import lombok.Builder; -import java.util.List; +import lombok.Builder; @Builder -public record EventHallResponse(Long id, String name, String address, List eventHallSeatResponses) { - static public EventHallResponse of(EventHall eventHall, List eventHallSeatResponses){ - return EventHallResponse.builder() - .id(eventHall.getId()) - .name(eventHall.getName()) - .address(eventHall.getAddress()) - .eventHallSeatResponses(eventHallSeatResponses) - .build(); - } +public record EventHallResponse( + Long id, + String name, + String address, + List eventHallSeatResponses) { + + public static EventHallResponse of(EventHall eventHall, List eventHallSeatResponses) { + return EventHallResponse.builder() + .id(eventHall.getId()) + .name(eventHall.getName()) + .address(eventHall.getAddress()) + .eventHallSeatResponses(eventHallSeatResponses) + .build(); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallSeatResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallSeatResponse.java index 45931c6a..e8f25900 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallSeatResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/response/EventHallSeatResponse.java @@ -1,14 +1,16 @@ package com.pgms.apievent.eventHall.dto.response; import com.pgms.coredomain.domain.event.EventHallSeat; + import lombok.Builder; @Builder public record EventHallSeatResponse(Long id, String name) { - static public EventHallSeatResponse of(EventHallSeat eventHallSeat){ - return EventHallSeatResponse.builder() - .id(eventHallSeat.getId()) - .name(eventHallSeat.getName()) - .build(); - } + + public static EventHallSeatResponse of(EventHallSeat eventHallSeat) { + return EventHallSeatResponse.builder() + .id(eventHallSeat.getId()) + .name(eventHallSeat.getName()) + .build(); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java index 53cbe19c..3a3efdb7 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java @@ -1,105 +1,111 @@ package com.pgms.apievent.eventHall.service; +import static com.pgms.apievent.exception.EventErrorCode.*; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.pgms.apievent.eventHall.dto.request.EventHallCreateRequest; -import com.pgms.apievent.eventHall.dto.request.EventHallUpdateRequest; import com.pgms.apievent.eventHall.dto.request.EventHallSeatCreateRequest; +import com.pgms.apievent.eventHall.dto.request.EventHallUpdateRequest; import com.pgms.apievent.eventHall.dto.response.EventHallResponse; import com.pgms.apievent.eventHall.dto.response.EventHallSeatResponse; -import com.pgms.apievent.exception.EventHallNotFoundException; +import com.pgms.apievent.exception.EventException; import com.pgms.coredomain.domain.event.EventHall; import com.pgms.coredomain.domain.event.EventHallEdit; import com.pgms.coredomain.domain.event.EventHallSeat; import com.pgms.coredomain.domain.event.repository.EventHallRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.List; +import lombok.RequiredArgsConstructor; @Service @Transactional @RequiredArgsConstructor public class EventHallService { - private final EventHallRepository eventHallRepository; + private final EventHallRepository eventHallRepository; - public EventHallResponse createEventHall(EventHallCreateRequest eventHallCreateRequest) { - List eventHallSeatCreateRequests = eventHallCreateRequest.eventHallSeatCreateRequests(); + public EventHallResponse createEventHall(EventHallCreateRequest eventHallCreateRequest) { + List eventHallSeatCreateRequests = eventHallCreateRequest.eventHallSeatCreateRequests(); - List eventHallSeats = eventHallSeatCreateRequests.stream() - .map(this::createEventHallSeat) - .toList(); + List eventHallSeats = eventHallSeatCreateRequests.stream() + .map(this::createEventHallSeat) + .toList(); - EventHall eventHall = EventHall.builder() - .name(eventHallCreateRequest.name()) - .address(eventHallCreateRequest.address()) - .eventHallSeats(eventHallSeats) - .build(); + EventHall eventHall = EventHall.builder() + .name(eventHallCreateRequest.name()) + .address(eventHallCreateRequest.address()) + .eventHallSeats(eventHallSeats) + .build(); - EventHall savedEventHall = eventHallRepository.save(eventHall); + EventHall savedEventHall = eventHallRepository.save(eventHall); - List eventHallSeatResponses = savedEventHall.getEventHallSeats() - .stream() - .map(EventHallSeatResponse::of) - .toList(); + List eventHallSeatResponses = savedEventHall.getEventHallSeats() + .stream() + .map(EventHallSeatResponse::of) + .toList(); - return EventHallResponse.of(savedEventHall, eventHallSeatResponses); - } + return EventHallResponse.of(savedEventHall, eventHallSeatResponses); + } - public void deleteEventHall(Long id) { - EventHall eventHall = eventHallRepository.findById(id) - .orElseThrow(RuntimeException::new); + public void deleteEventHall(Long id) { + EventHall eventHall = eventHallRepository.findById(id) + .orElseThrow(RuntimeException::new); - eventHallRepository.delete(eventHall); - } + eventHallRepository.delete(eventHall); + } - public EventHallResponse updateEventHall(Long id, EventHallUpdateRequest eventHallUpdateRequest) { - EventHall eventHall = eventHallRepository.findById(id).orElseThrow(EventHallNotFoundException::new); + public EventHallResponse updateEventHall(Long id, EventHallUpdateRequest eventHallUpdateRequest) { + EventHall eventHall = eventHallRepository.findById(id) + .orElseThrow(() -> new EventException(EVENT_HALL_NOT_FOUND)); - List eventHallSeatCreateRequests = eventHallUpdateRequest.eventHallSeatCreateRequests(); + List eventHallSeatCreateRequests = eventHallUpdateRequest.eventHallSeatCreateRequests(); - List eventHallSeats = eventHallSeatCreateRequests.stream() - .map(this::createEventHallSeat) - .toList(); + List eventHallSeats = eventHallSeatCreateRequests.stream() + .map(this::createEventHallSeat) + .toList(); - EventHallEdit eventHallEdit = EventHallEdit.builder() - .name(eventHallUpdateRequest.name()) - .address(eventHallUpdateRequest.address()) - .eventHallSeats(eventHallSeats) - .build(); + EventHallEdit eventHallEdit = EventHallEdit.builder() + .name(eventHallUpdateRequest.name()) + .address(eventHallUpdateRequest.address()) + .eventHallSeats(eventHallSeats) + .build(); - eventHall.updateEventHall(eventHallEdit); + eventHall.updateEventHall(eventHallEdit); - List eventHallSeatResponses = eventHall.getEventHallSeats() - .stream() - .map(EventHallSeatResponse::of) - .toList(); + List eventHallSeatResponses = eventHall.getEventHallSeats() + .stream() + .map(EventHallSeatResponse::of) + .toList(); - return EventHallResponse.of(eventHall, eventHallSeatResponses); - } + return EventHallResponse.of(eventHall, eventHallSeatResponses); + } - @Transactional(readOnly = true) - public EventHallResponse getEventHall(Long id) { - EventHall eventHall = eventHallRepository.findById(id).orElseThrow(EventHallNotFoundException::new); + @Transactional(readOnly = true) + public EventHallResponse getEventHall(Long id) { + EventHall eventHall = eventHallRepository.findById(id) + .orElseThrow(() -> new EventException(EVENT_HALL_NOT_FOUND)); - // TODO 못 읽어옴 - List eventHallSeatResponses = eventHall.getEventHallSeats() - .stream() - .map(EventHallSeatResponse::of) - .toList(); + // TODO 못 읽어옴 + List eventHallSeatResponses = eventHall.getEventHallSeats() + .stream() + .map(EventHallSeatResponse::of) + .toList(); - return EventHallResponse.of(eventHall, eventHallSeatResponses); - } + return EventHallResponse.of(eventHall, eventHallSeatResponses); + } - @Transactional(readOnly = true) - public List getEventHalls() { - List eventHalls = eventHallRepository.findAll(); - return eventHalls.stream() - .map(eventHall -> EventHallResponse.of(eventHall, null)) - .toList(); - } + @Transactional(readOnly = true) + public List getEventHalls() { + List eventHalls = eventHallRepository.findAll(); + return eventHalls.stream() + .map(eventHall -> EventHallResponse.of(eventHall, null)) + .toList(); + } - private EventHallSeat createEventHallSeat(EventHallSeatCreateRequest eventHallSeatCreateRequest) { - return new EventHallSeat(eventHallSeatCreateRequest.name()); - } + private EventHallSeat createEventHallSeat(EventHallSeatCreateRequest eventHallSeatCreateRequest) { + return new EventHallSeat(eventHallSeatCreateRequest.name()); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java index 1d0291ec..fa030f26 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java @@ -1,18 +1,20 @@ package com.pgms.apievent.eventSearch.controller; -import com.pgms.apievent.common.dto.response.PageResponseDto; -import com.pgms.apievent.eventSearch.dto.request.EventKeywordSearchRequest; -import com.pgms.apievent.eventSearch.service.EventSearchService; -import com.pgms.coredomain.response.ApiResponse; -import com.pgms.coreinfraes.dto.TopTenSearchResponse; -import lombok.RequiredArgsConstructor; +import java.util.List; + import org.springframework.http.ResponseEntity; 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 com.pgms.apievent.common.dto.response.PageResponseDto; +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.coredomain.response.ApiResponse; + +import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/api/v1/events/search") @@ -30,7 +32,7 @@ public ResponseEntity searchEventsByKeyword( @GetMapping("/top-ten") ResponseEntity getRecentTop10Keywords() { - List response = eventSearchService.getRecentTop10Keywords(); + List response = eventSearchService.getRecentTop10Keywords(); return ResponseEntity.ok(ApiResponse.ok(response)); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java index bbb19755..51ea15db 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java @@ -1,10 +1,11 @@ package com.pgms.apievent.eventSearch.dto.request; +import java.util.List; + import com.pgms.apievent.common.dto.request.PageRequestDto; import com.pgms.coreinfraes.dto.EventKeywordSearchDto; -import lombok.Getter; -import java.util.List; +import lombok.Getter; @Getter public class EventKeywordSearchRequest extends PageRequestDto { @@ -13,7 +14,14 @@ public class EventKeywordSearchRequest extends PageRequestDto { private String startedAt; private String endedAt; - public EventKeywordSearchRequest(Integer page, Integer size, String keyword, List genreType, String startedAt, String endedAt) { + public EventKeywordSearchRequest( + Integer page, + Integer size, + String keyword, + List genreType, + String startedAt, + String endedAt + ) { super(page, size); this.keyword = keyword; this.genreType = genreType; diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/RecentTop10KeywordsResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/RecentTop10KeywordsResponse.java index 80a4dde9..8308eeed 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/RecentTop10KeywordsResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/RecentTop10KeywordsResponse.java @@ -1,4 +1,9 @@ package com.pgms.apievent.eventSearch.dto.response; +import com.pgms.coreinfraes.dto.TopTenSearchResponse; + public record RecentTop10KeywordsResponse(String keyword) { + public static RecentTop10KeywordsResponse of(TopTenSearchResponse topTenSearchResponse) { + return new RecentTop10KeywordsResponse(topTenSearchResponse.keyword()); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/service/EventSearchService.java b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/service/EventSearchService.java index a26036dd..1f8f07f8 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/service/EventSearchService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/service/EventSearchService.java @@ -1,17 +1,20 @@ package com.pgms.apievent.eventSearch.service; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.pgms.apievent.common.dto.response.PageResponseDto; import com.pgms.apievent.eventSearch.dto.request.EventKeywordSearchRequest; -import com.pgms.coreinfraes.document.EventDocument; +import com.pgms.apievent.eventSearch.dto.response.RecentTop10KeywordsResponse; +import com.pgms.coreinfraes.dto.EventDocumentResponse; import com.pgms.coreinfraes.dto.EventKeywordSearchDto; import com.pgms.coreinfraes.dto.TopTenSearchResponse; import com.pgms.coreinfraes.repository.EventSearchQueryRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.List; +import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor @@ -22,13 +25,13 @@ public class EventSearchService { @Transactional(readOnly = true) public PageResponseDto searchEventsByKeyword(EventKeywordSearchRequest request) { EventKeywordSearchDto eventKeywordSearchDto = request.toDto(); - Page eventDocuments = eventSearchQueryRepository.findByKeyword( - eventKeywordSearchDto); + Page eventDocuments = eventSearchQueryRepository.findByKeyword(eventKeywordSearchDto); return PageResponseDto.of(eventDocuments); } @Transactional(readOnly = true) - public List getRecentTop10Keywords() { - return eventSearchQueryRepository.getRecentTop10Keywords(); + public List getRecentTop10Keywords() { + List recentTop10Keywords = eventSearchQueryRepository.getRecentTop10Keywords(); + return recentTop10Keywords.stream().map(RecentTop10KeywordsResponse::of).toList(); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/request/EventSeatsCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/request/EventSeatsCreateRequest.java index 9394bd61..156b6277 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/request/EventSeatsCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/request/EventSeatsCreateRequest.java @@ -1,11 +1,12 @@ package com.pgms.apievent.eventSeat.dto.request; -import com.pgms.coredomain.domain.event.EventSeatStatus; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; -public record EventSeatsCreateRequest(@NotBlank(message = "공연장 좌석은 빈칸을 지정할 수 없습니다.") - @Size(max = 10, message = "공연장 좌석은 10자 이내로 입력해주세요.") String name, - EventSeatStatus status, - Long eventSeatAreaId) { +public record EventSeatsCreateRequest( + @NotBlank(message = "공연장 좌석은 빈칸을 지정할 수 없습니다.") + @Size(max = 10, message = "공연장 좌석은 10자 이내로 입력해주세요.") + String name, + String status, + Long eventSeatAreaId) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/EventSeatResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/EventSeatResponse.java index a38a9d2f..e36dd4c0 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/EventSeatResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/EventSeatResponse.java @@ -5,13 +5,13 @@ import com.pgms.coredomain.domain.event.EventSeatStatus; public record EventSeatResponse(Long id, - String name, - EventSeatStatus status, - EventSeatAreaResponse eventSeatAreaResponse) { - public static EventSeatResponse of(EventSeat eventSeat){ - return new EventSeatResponse(eventSeat.getId(), - eventSeat.getName(), - eventSeat.getStatus(), - EventSeatAreaResponse.of(eventSeat.getEventSeatArea())); - } + String name, + EventSeatStatus status, + EventSeatAreaResponse eventSeatAreaResponse) { + public static EventSeatResponse of(EventSeat eventSeat) { + return new EventSeatResponse(eventSeat.getId(), + eventSeat.getName(), + eventSeat.getStatus(), + EventSeatAreaResponse.of(eventSeat.getEventSeatArea())); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/LeftEventSeatResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/LeftEventSeatResponse.java index 7b5eafc9..9e818807 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/LeftEventSeatResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/dto/response/LeftEventSeatResponse.java @@ -2,6 +2,5 @@ import com.pgms.coredomain.domain.event.SeatAreaType; -public record LeftEventSeatResponse(SeatAreaType seatAreaType, - Long leftSeatNumber) { +public record LeftEventSeatResponse(SeatAreaType seatAreaType, Long leftSeatNumber) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/service/EventSeatService.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/service/EventSeatService.java index 1c3c4fb6..c2eff6ee 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeat/service/EventSeatService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeat/service/EventSeatService.java @@ -1,98 +1,101 @@ package com.pgms.apievent.eventSeat.service; +import static com.pgms.apievent.exception.EventErrorCode.*; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import com.pgms.apievent.eventSeat.dto.request.EventSeatsCreateRequest; import com.pgms.apievent.eventSeat.dto.response.EventSeatResponse; import com.pgms.apievent.eventSeat.dto.response.LeftEventSeatResponse; import com.pgms.apievent.eventSeat.repository.EventSeatCustomRepository; -import com.pgms.apievent.exception.CustomException; -import com.pgms.apievent.exception.EventSeatAreaNotFoundException; -import com.pgms.coredomain.domain.event.*; +import com.pgms.apievent.exception.EventException; +import com.pgms.coredomain.domain.event.Event; +import com.pgms.coredomain.domain.event.EventSeat; +import com.pgms.coredomain.domain.event.EventSeatArea; +import com.pgms.coredomain.domain.event.EventSeatStatus; +import com.pgms.coredomain.domain.event.EventTime; import com.pgms.coredomain.domain.event.repository.EventRepository; import com.pgms.coredomain.domain.event.repository.EventSeatAreaRepository; import com.pgms.coredomain.domain.event.repository.EventSeatRepository; import com.pgms.coredomain.domain.event.repository.EventTimeRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.List; - -import static com.pgms.apievent.exception.EventErrorCode.EVENT_NOT_FOUND; -import static com.pgms.apievent.exception.EventErrorCode.EVENT_TIME_NOT_FOUND; +import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class EventSeatService { - private final EventRepository eventRepository; - private final EventTimeRepository eventTimeRepository; - private final EventSeatRepository eventSeatRepository; - private final EventSeatAreaRepository eventSeatAreaRepository; - private final EventSeatCustomRepository eventSeatCustomRepository; - - public void createEventSeats(Long id, List eventSeatsCreateRequests) { - Event event = eventRepository.findById(id) - .orElseThrow(() -> new CustomException(EVENT_NOT_FOUND)); - List eventTimes = eventTimeRepository.findEventTimesByEvent(event); - - List> eventSeatsByEventTimes = eventTimes.stream() - .map(eventTime -> - eventSeatsCreateRequests - .stream() - .map(request -> { - EventSeatArea eventSeatArea = eventSeatAreaRepository - .findById(request.eventSeatAreaId()) - .orElseThrow(EventSeatAreaNotFoundException::new); - - return EventSeat.builder() - .eventTime(eventTime) - .status(request.status()) - .eventSeatArea(eventSeatArea) - .name(request.name()) - .build(); - }) - .toList()) - .toList(); - - eventSeatsByEventTimes - .forEach(eventSeatRepository::saveAll); - } - - public void updateEventSeatsSeatArea(List ids, Long seatAreaId) { - EventSeatArea eventSeatArea = eventSeatAreaRepository.findById(seatAreaId) - .orElseThrow(EventSeatAreaNotFoundException::new); - - eventSeatCustomRepository.updateEventSeatsSeatArea(ids.toArray(new Long[0]), eventSeatArea); - } - - public void updateEventSeatsStatus(List ids, EventSeatStatus eventSeatStatus) { - eventSeatCustomRepository.updateEventSeatsStatus(ids.toArray(new Long[0]), eventSeatStatus); - } - - public void deleteEventSeats(List ids) { - eventSeatCustomRepository.deleteEventSeats(ids.toArray(new Long[0])); - } - - @Transactional(readOnly = true) - public List getEventSeatsByEventTime(Long id) { - List eventSeats = eventSeatRepository.findAllWithAreaByTimeId(id); - - List eventSeatResponses = eventSeats.stream() - .map(EventSeatResponse::of) - .toList(); - - return eventSeatResponses; - } - - @Transactional(readOnly = true) - public List getLeftEventSeatNumberByEventTime(Long id) { - EventTime eventTime = eventTimeRepository.findById(id) - .orElseThrow(() -> new CustomException(EVENT_TIME_NOT_FOUND)); - - return eventSeatCustomRepository.getLeftEventSeatNumberByEventTime(eventTime) - .stream() - .map(leftEventSeatNumDto -> - new LeftEventSeatResponse(leftEventSeatNumDto.getEventSeatArea().getSeatAreaType(), leftEventSeatNumDto.getLeftSeatNumber())) - .toList(); - } + private final EventRepository eventRepository; + private final EventTimeRepository eventTimeRepository; + private final EventSeatRepository eventSeatRepository; + private final EventSeatAreaRepository eventSeatAreaRepository; + private final EventSeatCustomRepository eventSeatCustomRepository; + + public void createEventSeats(Long id, List eventSeatsCreateRequests) { + Event event = eventRepository.findById(id) + .orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); + List eventTimes = eventTimeRepository.findEventTimesByEvent(event); + + List> eventSeatsByEventTimes = eventTimes.stream() + .map(eventTime -> + eventSeatsCreateRequests + .stream() + .map(request -> { + EventSeatArea eventSeatArea = eventSeatAreaRepository + .findById(request.eventSeatAreaId()) + .orElseThrow(() -> new EventException(EVENT_SEAT_AREA_NOT_FOUND)); + + return EventSeat.builder() + .eventTime(eventTime) + .status(EventSeatStatus.valueOf(request.status())) + .eventSeatArea(eventSeatArea) + .name(request.name()) + .build(); + }) + .toList()) + .toList(); + + eventSeatsByEventTimes + .forEach(eventSeatRepository::saveAll); + } + + public void updateEventSeatsSeatArea(List ids, Long seatAreaId) { + EventSeatArea eventSeatArea = eventSeatAreaRepository.findById(seatAreaId) + .orElseThrow(() -> new EventException(EVENT_SEAT_AREA_NOT_FOUND)); + + eventSeatCustomRepository.updateEventSeatsSeatArea(ids.toArray(new Long[0]), eventSeatArea); + } + + public void updateEventSeatsStatus(List ids, EventSeatStatus eventSeatStatus) { + eventSeatCustomRepository.updateEventSeatsStatus(ids.toArray(new Long[0]), eventSeatStatus); + } + + public void deleteEventSeats(List ids) { + eventSeatCustomRepository.deleteEventSeats(ids.toArray(new Long[0])); + } + + @Transactional(readOnly = true) + public List getEventSeatsByEventTime(Long id) { + List eventSeats = eventSeatRepository.findAllWithAreaByTimeId(id); + + return eventSeats.stream() + .map(EventSeatResponse::of) + .toList(); + } + + @Transactional(readOnly = true) + public List getLeftEventSeatNumberByEventTime(Long id) { + EventTime eventTime = eventTimeRepository.findById(id) + .orElseThrow(() -> new EventException(EVENT_TIME_NOT_FOUND)); + + return eventSeatCustomRepository.getLeftEventSeatNumberByEventTime(eventTime) + .stream() + .map(leftEventSeatNumDto -> + new LeftEventSeatResponse(leftEventSeatNumDto.getEventSeatArea().getSeatAreaType(), + leftEventSeatNumDto.getLeftSeatNumber())) + .toList(); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java index 02258778..1589b0ae 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java @@ -28,7 +28,8 @@ public class EventSeatAreaController { private final EventSeatAreaService eventSeatAreaService; @PostMapping("/{id}/seat-area") - public ResponseEntity createEventSeatArea(@PathVariable Long id, + public ResponseEntity createEventSeatArea( + @PathVariable Long id, @RequestBody EventSeatAreaCreateRequest request) { List response = eventSeatAreaService.createEventSeatArea(id, request); return ResponseEntity.ok(ApiResponse.ok(response)); @@ -47,7 +48,8 @@ public ResponseEntity deleteEventSeatArea(@PathVariable Long areaId) { } @PutMapping("/seat-area/{areaId}") - public ResponseEntity updateEventSeatArea(@PathVariable Long areaId, + public ResponseEntity updateEventSeatArea( + @PathVariable Long areaId, @RequestBody EventSeatAreaUpdateRequest request) { eventSeatAreaService.updateEventSeatArea(areaId, request); return ResponseEntity.noContent().build(); diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java index 5d08d79f..a2083d6a 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java @@ -2,9 +2,8 @@ import java.util.List; -import com.pgms.coredomain.domain.event.SeatAreaType; - -public record EventSeatAreaCreateRequest(SeatAreaType seatAreaType, - Integer price, - List requests) { +public record EventSeatAreaCreateRequest( + String seatAreaType, + Integer price, + List requests) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java index 2e2177f0..4d0a0f5b 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java @@ -2,6 +2,5 @@ import com.pgms.coredomain.domain.event.SeatAreaType; -public record EventSeatAreaUpdateRequest(SeatAreaType seatAreaType, - Integer price) { +public record EventSeatAreaUpdateRequest(SeatAreaType seatAreaType, Integer price) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/service/EventSeatAreaService.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/service/EventSeatAreaService.java index 5e1ba710..d4c625e6 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/service/EventSeatAreaService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/service/EventSeatAreaService.java @@ -10,10 +10,10 @@ import com.pgms.apievent.eventSeatArea.dto.request.EventSeatAreaCreateRequest; import com.pgms.apievent.eventSeatArea.dto.request.EventSeatAreaUpdateRequest; import com.pgms.apievent.eventSeatArea.dto.response.EventSeatAreaResponse; -import com.pgms.apievent.exception.CustomException; -import com.pgms.apievent.exception.EventSeatAreaNotFoundException; +import com.pgms.apievent.exception.EventException; import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventSeatArea; +import com.pgms.coredomain.domain.event.SeatAreaType; import com.pgms.coredomain.domain.event.repository.EventRepository; import com.pgms.coredomain.domain.event.repository.EventSeatAreaRepository; @@ -31,7 +31,9 @@ public List createEventSeatArea(Long id, EventSeatAreaCre Event event = getEvent(id); List eventSeatAreas = request.requests().stream() - .map(areaRequest -> new EventSeatArea(areaRequest.seatAreaType(), areaRequest.price(), event)) + .map(areaRequest -> new EventSeatArea( + SeatAreaType.valueOf(areaRequest.seatAreaType()), + areaRequest.price(), event)) .toList(); List savedEventSeatAreas = eventSeatAreaRepository.saveAll(eventSeatAreas); @@ -63,11 +65,11 @@ public List getEventSeatAreas(Long id) { private Event getEvent(Long eventId) { return eventRepository.findById(eventId) - .orElseThrow(() -> new CustomException(EVENT_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); } private EventSeatArea getEventSeatArea(Long areaId) { return eventSeatAreaRepository.findById(areaId) - .orElseThrow(EventSeatAreaNotFoundException::new); + .orElseThrow(() -> new EventException(EVENT_SEAT_AREA_NOT_FOUND)); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java index fe20cc90..646db945 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java @@ -39,6 +39,7 @@ public ResponseEntity createEventReview( .path("/{id}") .buildAndExpand(response.id()) .toUri(); + return ResponseEntity.created(location).body(ApiResponse.ok(response)); } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java index 6dd58cea..910d7b5d 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java @@ -11,8 +11,8 @@ public record EventReviewCreateRequest( int score, @Size(max = 1000, message = "공연 리뷰 내용은 최대 1,000자까지 입력 가능 합니다.") - String content -) { + String content) { + public EventReview toEntity(Event event) { return new EventReview( score, diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/response/EventReviewResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/response/EventReviewResponse.java index 90e31fd2..62216395 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/response/EventReviewResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/response/EventReviewResponse.java @@ -6,8 +6,8 @@ public record EventReviewResponse( Long id, int score, String content, - Long eventId -) { + Long eventId) { + public static EventReviewResponse of(EventReview eventReview) { return new EventReviewResponse( eventReview.getId(), diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java index 7f8aefea..0033126b 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java @@ -1,5 +1,7 @@ package com.pgms.apievent.eventreview.service; +import static com.pgms.apievent.exception.EventErrorCode.*; + import java.util.List; import org.springframework.stereotype.Service; @@ -8,8 +10,7 @@ import com.pgms.apievent.eventreview.dto.request.EventReviewCreateRequest; import com.pgms.apievent.eventreview.dto.request.EventReviewUpdateRequest; import com.pgms.apievent.eventreview.dto.response.EventReviewResponse; -import com.pgms.apievent.exception.CustomException; -import com.pgms.apievent.exception.EventErrorCode; +import com.pgms.apievent.exception.EventException; import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventReview; import com.pgms.coredomain.domain.event.repository.EventRepository; @@ -27,17 +28,18 @@ public class EventReviewService { public EventReviewResponse createEventReview(Long eventId, EventReviewCreateRequest request) { Event event = eventRepository.findById(eventId). - orElseThrow(() -> new CustomException(EventErrorCode.EVENT_NOT_FOUND)); + orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); EventReview eventReview = eventReviewRepository.save(request.toEntity(event)); - // TODO : 동시성 고려 생각해보기 + Double averageScore = eventReviewRepository.findAverageScoreByEvent(event.getId()); event.updateAverageScore(averageScore); + return EventReviewResponse.of(eventReview); } public EventReviewResponse updateEventReview(Long reviewId, EventReviewUpdateRequest request) { EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new CustomException(EventErrorCode.EVENT_REVIEW_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); eventReview.updateEventReview(request.content()); return EventReviewResponse.of(eventReview); } @@ -45,19 +47,21 @@ public EventReviewResponse updateEventReview(Long reviewId, EventReviewUpdateReq @Transactional(readOnly = true) public EventReviewResponse getEventReviewById(Long reviewId) { EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new CustomException(EventErrorCode.EVENT_REVIEW_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); return EventReviewResponse.of(eventReview); } @Transactional(readOnly = true) public List getEventReviewsForEventByEventId(Long eventId) { List eventReviews = eventReviewRepository.findEventReviewsByEventId(eventId); - return eventReviews.stream().map(EventReviewResponse::of).toList(); + return eventReviews.stream() + .map(EventReviewResponse::of) + .toList(); } public void deleteEventReviewById(Long reviewId) { EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new CustomException(EventErrorCode.EVENT_REVIEW_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); eventReviewRepository.delete(eventReview); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java index 4d8c4aab..e5410e3f 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java @@ -2,8 +2,6 @@ import java.time.LocalDateTime; -import org.springframework.format.annotation.DateTimeFormat; - import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventTime; @@ -13,13 +11,9 @@ public record EventTimeCreateRequest( @Positive(message = "회차는 0보다 큰 값 이어야 합니다.") int round, - - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, - - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") - LocalDateTime endTime -) { + LocalDateTime endTime) { + public EventTime toEntity(Event event) { return new EventTime( round, diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java index 85347949..e08892a8 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java @@ -2,13 +2,5 @@ import java.time.LocalDateTime; -import org.springframework.format.annotation.DateTimeFormat; - -public record EventTimeUpdateRequest( - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") - LocalDateTime startTime, - - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") - LocalDateTime endTime -) { +public record EventTimeUpdateRequest(LocalDateTime startTime, LocalDateTime endTime) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java index 93188078..5fd03a8a 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java @@ -9,8 +9,8 @@ public record EventTimeResponse( Long eventId, int round, LocalDateTime startTime, - LocalDateTime endTime -) { + LocalDateTime endTime) { + public static EventTimeResponse of(EventTime eventTime) { return new EventTimeResponse( eventTime.getId(), diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java index 2ef0526e..3fb6397d 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java @@ -10,7 +10,7 @@ import com.pgms.apievent.eventtime.dto.request.EventTimeCreateRequest; import com.pgms.apievent.eventtime.dto.request.EventTimeUpdateRequest; import com.pgms.apievent.eventtime.dto.response.EventTimeResponse; -import com.pgms.apievent.exception.CustomException; +import com.pgms.apievent.exception.EventException; import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventTime; import com.pgms.coredomain.domain.event.repository.EventRepository; @@ -29,7 +29,7 @@ public class EventTimeService { public EventTimeResponse createEventTime(Long eventId, EventTimeCreateRequest request) { validateEventTimeRoundAlreadyExist(eventId, request.round()); Event event = eventRepository.findById(eventId) - .orElseThrow(() -> new CustomException(EVENT_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); EventTime eventTime = eventTimeRepository.save(request.toEntity(event)); return EventTimeResponse.of(eventTime); } @@ -37,7 +37,7 @@ public EventTimeResponse createEventTime(Long eventId, EventTimeCreateRequest re @Transactional(readOnly = true) public EventTimeResponse getEventTimeById(Long eventTimeId) { EventTime eventTime = eventTimeRepository.findById(eventTimeId) - .orElseThrow(() -> new CustomException(EVENT_TIME_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_TIME_NOT_FOUND)); return EventTimeResponse.of(eventTime); } @@ -49,7 +49,7 @@ public List getEventTimesByEventId(Long eventId) { public EventTimeResponse updateEventTime(Long eventTimeId, EventTimeUpdateRequest request) { EventTime eventTime = eventTimeRepository.findById(eventTimeId) - .orElseThrow(() -> new CustomException(EVENT_TIME_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_TIME_NOT_FOUND)); eventTime.updateEventTime(request.startTime(), request.endTime()); return EventTimeResponse.of(eventTime); @@ -57,13 +57,13 @@ public EventTimeResponse updateEventTime(Long eventTimeId, EventTimeUpdateReques public void deleteEventTimeById(Long eventTimeId) { EventTime eventTime = eventTimeRepository.findById(eventTimeId) - .orElseThrow(() -> new CustomException(EVENT_TIME_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_TIME_NOT_FOUND)); eventTimeRepository.delete(eventTime); } private void validateEventTimeRoundAlreadyExist(Long eventId, int round) { if (eventTimeRepository.existsEventTimeForEventByRound(eventId, round)) { - throw new CustomException(ALREADY_EXIST_EVENT_TIME); + throw new EventException(ALREADY_EXIST_EVENT_TIME); } } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/CustomException.java b/api/api-event/src/main/java/com/pgms/apievent/exception/CustomException.java deleted file mode 100644 index 70f1930b..00000000 --- a/api/api-event/src/main/java/com/pgms/apievent/exception/CustomException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.pgms.apievent.exception; - -import lombok.Getter; - -@Getter -public class CustomException extends RuntimeException { - - private final EventErrorCode errorCode; - - public CustomException(EventErrorCode errorCode) { - super(errorCode.getMessage()); - this.errorCode = errorCode; - } -} diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java b/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java index e8821525..80c30e15 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java +++ b/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java @@ -2,10 +2,13 @@ import org.springframework.http.HttpStatus; +import com.pgms.coredomain.domain.common.BaseErrorCode; +import com.pgms.coredomain.response.ErrorResponse; + import lombok.Getter; @Getter -public enum EventErrorCode { +public enum EventErrorCode implements BaseErrorCode { EVENT_NOT_FOUND("NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 공연입니다."), EVENT_HALL_NOT_FOUND("EVENT HALL NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 공연장입니다."), @@ -27,4 +30,9 @@ public enum EventErrorCode { this.status = status; this.message = message; } + + @Override + public ErrorResponse getErrorResponse() { + return new ErrorResponse(errorCode, message); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/EventException.java b/api/api-event/src/main/java/com/pgms/apievent/exception/EventException.java new file mode 100644 index 00000000..cb58d5b3 --- /dev/null +++ b/api/api-event/src/main/java/com/pgms/apievent/exception/EventException.java @@ -0,0 +1,16 @@ +package com.pgms.apievent.exception; + +import com.pgms.coredomain.domain.common.BaseErrorCode; + +import lombok.Getter; + +@Getter +public class EventException extends RuntimeException { + + private final BaseErrorCode errorCode; + + public EventException(BaseErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/EventGlobalExceptionHandler.java b/api/api-event/src/main/java/com/pgms/apievent/exception/EventGlobalExceptionHandler.java index a2b72568..1b5360e0 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/exception/EventGlobalExceptionHandler.java +++ b/api/api-event/src/main/java/com/pgms/apievent/exception/EventGlobalExceptionHandler.java @@ -1,7 +1,10 @@ package com.pgms.apievent.exception; -import com.pgms.coredomain.response.ErrorResponse; -import lombok.extern.slf4j.Slf4j; +import static com.pgms.apievent.exception.EventErrorCode.*; + +import java.util.List; +import java.util.Objects; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; @@ -10,10 +13,10 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import java.util.List; -import java.util.Objects; +import com.pgms.coredomain.domain.common.BaseErrorCode; +import com.pgms.coredomain.response.ErrorResponse; -import static com.pgms.apievent.exception.EventErrorCode.VALIDATION_FAILED; +import lombok.extern.slf4j.Slf4j; @Slf4j @RestControllerAdvice @@ -26,12 +29,11 @@ protected ResponseEntity handleGlobalException(Exception ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); } - @ExceptionHandler(CustomException.class) - protected ResponseEntity handleEventCustomException(CustomException ex) { + @ExceptionHandler(EventException.class) + protected ResponseEntity handleEventCustomException(EventException ex) { log.warn(">>>>> Custom Exception : {}", ex); - EventErrorCode errorCode = ex.getErrorCode(); - ErrorResponse errorResponse = new ErrorResponse(errorCode.getErrorCode(), errorCode.getMessage()); - return ResponseEntity.status(errorCode.getStatus()).body(errorResponse); + BaseErrorCode errorCode = ex.getErrorCode(); + return ResponseEntity.status(errorCode.getStatus()).body(errorCode.getErrorResponse()); } @ExceptionHandler(MethodArgumentNotValidException.class) diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/EventHallNotFoundException.java b/api/api-event/src/main/java/com/pgms/apievent/exception/EventHallNotFoundException.java deleted file mode 100644 index ef162d31..00000000 --- a/api/api-event/src/main/java/com/pgms/apievent/exception/EventHallNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.pgms.apievent.exception; - -import static com.pgms.apievent.exception.EventErrorCode.EVENT_HALL_NOT_FOUND; - -public class EventHallNotFoundException extends CustomException{ - public EventHallNotFoundException() { - super(EVENT_HALL_NOT_FOUND); - } -} diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/EventSeatAreaNotFoundException.java b/api/api-event/src/main/java/com/pgms/apievent/exception/EventSeatAreaNotFoundException.java deleted file mode 100644 index a66cce1d..00000000 --- a/api/api-event/src/main/java/com/pgms/apievent/exception/EventSeatAreaNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.pgms.apievent.exception; - -public class EventSeatAreaNotFoundException extends CustomException{ - public EventSeatAreaNotFoundException() { - super(EventErrorCode.EVENT_SEAT_AREA_NOT_FOUND); - } -} diff --git a/api/api-event/src/main/java/com/pgms/apievent/image/service/EventImageService.java b/api/api-event/src/main/java/com/pgms/apievent/image/service/EventImageService.java index 37f96074..dd4fc898 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/image/service/EventImageService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/image/service/EventImageService.java @@ -11,7 +11,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import com.pgms.apievent.exception.CustomException; +import com.pgms.apievent.exception.EventException; import com.pgms.apievent.image.dto.request.EventImageCreateRequest; import com.pgms.apievent.image.dto.request.ThumbnailUpdateRequest; import com.pgms.apievent.util.ImageUtil; @@ -68,7 +68,7 @@ public void removeEventImages(Long id, List removedImageIds) { private Event getEvent(Long eventId) { return eventRepository.findById(eventId) - .orElseThrow(() -> new CustomException(EVENT_NOT_FOUND)); + .orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); } private URL uploadThumbnailImageToServer(MultipartFile file, String storedFileName) { diff --git a/api/api-event/src/main/java/com/pgms/apievent/image/service/S3Service.java b/api/api-event/src/main/java/com/pgms/apievent/image/service/S3Service.java index 64f49189..6245aca3 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/image/service/S3Service.java +++ b/api/api-event/src/main/java/com/pgms/apievent/image/service/S3Service.java @@ -12,7 +12,7 @@ import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.DeleteObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; -import com.pgms.apievent.exception.CustomException; +import com.pgms.apievent.exception.EventException; import lombok.RequiredArgsConstructor; @@ -32,7 +32,7 @@ public URL upload(MultipartFile file, String filename) { amazonS3Client.putObject(bucket, filename, file.getInputStream(), metadata); return amazonS3Client.getUrl(bucket, filename); } catch (IOException e) { - throw new CustomException(S3_UPLOAD_FAILED_EXCEPTION); + throw new EventException(S3_UPLOAD_FAILED_EXCEPTION); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/scheduler/EventDocumentScheduler.java b/api/api-event/src/main/java/com/pgms/apievent/scheduler/EventDocumentScheduler.java index f9de1bb9..e58ed7f8 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/scheduler/EventDocumentScheduler.java +++ b/api/api-event/src/main/java/com/pgms/apievent/scheduler/EventDocumentScheduler.java @@ -1,42 +1,44 @@ package com.pgms.apievent.scheduler; +import java.util.List; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + import com.pgms.coreinfraes.buffer.DocumentBuffer; import com.pgms.coreinfraes.repository.EventSearchQueryRepository; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.util.List; @Slf4j @Component @RequiredArgsConstructor public class EventDocumentScheduler { - private static final int SCHEDULE_UPDATE_CYCLE = 30000; - - private final EventSearchQueryRepository eventSearchQueryRepository; - - @Scheduled(fixedDelay = SCHEDULE_UPDATE_CYCLE) - public void scheduleUpdateEventDocument() { - log.info(">>>>> execute scheduleUpdateEventDocument"); - try { - List documents = DocumentBuffer.getAll(); - isDocumentsEmpty(documents); - eventSearchQueryRepository.bulkUpdate(documents); - } catch (RuntimeException e){ - log.warn(">>>>> scheduleUpdateEventDocument Document Empty"); - return; - } catch (Exception e){ - log.warn(">>>>> scheduleUpdateEventDocument failed!!! {}", e); - return; - } - DocumentBuffer.deleteAll(); - } - - private static void isDocumentsEmpty(List documents) { - if(documents.isEmpty()) - throw new RuntimeException("scheduleUpdateEventDocument Document Emtpy"); - } + private static final int SCHEDULE_UPDATE_CYCLE = 30000; + + private final EventSearchQueryRepository eventSearchQueryRepository; + + @Scheduled(fixedDelay = SCHEDULE_UPDATE_CYCLE) + public void scheduleUpdateEventDocument() { + log.info(">>>>> execute scheduleUpdateEventDocument"); + try { + List documents = DocumentBuffer.getAll(); + isDocumentsEmpty(documents); + eventSearchQueryRepository.bulkUpdate(documents); + } catch (RuntimeException e) { + log.warn(">>>>> scheduleUpdateEventDocument: Document Empty"); + return; + } catch (Exception e) { + log.warn(">>>>> scheduleUpdateEventDocument: failed!!! {}", e); + return; + } + DocumentBuffer.deleteAll(); + } + + private static void isDocumentsEmpty(List documents) { + if (documents.isEmpty()) + throw new IllegalArgumentException("scheduleUpdateEventDocument: Document Emtpy"); + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/util/ImageUtil.java b/api/api-event/src/main/java/com/pgms/apievent/util/ImageUtil.java index 8a6a68a4..26b65e46 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/util/ImageUtil.java +++ b/api/api-event/src/main/java/com/pgms/apievent/util/ImageUtil.java @@ -5,7 +5,7 @@ import java.util.Arrays; import java.util.UUID; -import com.pgms.apievent.exception.CustomException; +import com.pgms.apievent.exception.EventException; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -14,7 +14,7 @@ public class ImageUtil { private static final int EXTENSION_START_INDEX = 1; - private static final String[] EXTENSION = {"jpg", "jpeg", "png"}; + private static final String[] EXTENSION = {"jpg", "jpeg", "png", "gif"}; public static String extractExtAndGenerateUniqueName(String originName) { return UUID.randomUUID() + "." + getExtension(originName); @@ -25,7 +25,7 @@ private static String getExtension(String originName) { if (supportFormat(extension)) { return extension; } - throw new CustomException(UNSUPPORTED_FILE_EXTENSION); + throw new EventException(UNSUPPORTED_FILE_EXTENSION); } private static boolean supportFormat(String ext) { diff --git a/api/api-event/src/test/java/com/pgms/apievent/eventSeat/service/EventSeatServiceTest.java b/api/api-event/src/test/java/com/pgms/apievent/eventSeat/service/EventSeatServiceTest.java index 3fd72c5e..0bcd386d 100644 --- a/api/api-event/src/test/java/com/pgms/apievent/eventSeat/service/EventSeatServiceTest.java +++ b/api/api-event/src/test/java/com/pgms/apievent/eventSeat/service/EventSeatServiceTest.java @@ -1,13 +1,12 @@ package com.pgms.apievent.eventSeat.service; -import com.pgms.apievent.EventTestConfig; -import com.pgms.apievent.eventSeat.dto.request.EventSeatsCreateRequest; -import com.pgms.apievent.eventSeat.dto.response.EventSeatResponse; -import com.pgms.apievent.eventSeat.dto.response.LeftEventSeatResponse; -import com.pgms.apievent.factory.event.EventFactory; -import com.pgms.apievent.factory.eventhall.EventHallFactory; -import com.pgms.coredomain.domain.event.*; -import com.pgms.coredomain.domain.event.repository.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.IntStream; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -15,12 +14,23 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.IntStream; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import com.pgms.apievent.EventTestConfig; +import com.pgms.apievent.eventSeat.dto.request.EventSeatsCreateRequest; +import com.pgms.apievent.eventSeat.dto.response.EventSeatResponse; +import com.pgms.apievent.eventSeat.dto.response.LeftEventSeatResponse; +import com.pgms.apievent.factory.event.EventFactory; +import com.pgms.apievent.factory.eventhall.EventHallFactory; +import com.pgms.coredomain.domain.event.Event; +import com.pgms.coredomain.domain.event.EventHall; +import com.pgms.coredomain.domain.event.EventSeatArea; +import com.pgms.coredomain.domain.event.EventSeatStatus; +import com.pgms.coredomain.domain.event.EventTime; +import com.pgms.coredomain.domain.event.SeatAreaType; +import com.pgms.coredomain.domain.event.repository.EventHallRepository; +import com.pgms.coredomain.domain.event.repository.EventRepository; +import com.pgms.coredomain.domain.event.repository.EventSeatAreaRepository; +import com.pgms.coredomain.domain.event.repository.EventSeatRepository; +import com.pgms.coredomain.domain.event.repository.EventTimeRepository; @Transactional @SpringBootTest @@ -68,7 +78,7 @@ void setUp() { List eventSeatsCreateRequests = IntStream.range(0, 20) .mapToObj(i -> - new EventSeatsCreateRequest("", EventSeatStatus.AVAILABLE, eventSeatArea.getId())) + new EventSeatsCreateRequest("", "AVAILABLE", eventSeatArea.getId())) .toList(); // when @@ -94,7 +104,7 @@ void setUp() { List eventSeatsCreateRequests = IntStream.range(0, 20) .mapToObj(i -> - new EventSeatsCreateRequest("", EventSeatStatus.AVAILABLE, eventSeatArea.getId())) + new EventSeatsCreateRequest("", "AVAILABLE", eventSeatArea.getId())) .toList(); eventSeatService.createEventSeats(event.getId(), eventSeatsCreateRequests); diff --git a/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java b/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java index 5550893b..2a4c194c 100644 --- a/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java +++ b/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java @@ -17,7 +17,7 @@ import com.pgms.apievent.eventtime.dto.request.EventTimeCreateRequest; import com.pgms.apievent.eventtime.dto.request.EventTimeUpdateRequest; import com.pgms.apievent.eventtime.dto.response.EventTimeResponse; -import com.pgms.apievent.exception.CustomException; +import com.pgms.apievent.exception.EventException; import com.pgms.apievent.factory.event.EventFactory; import com.pgms.apievent.factory.eventhall.EventHallFactory; import com.pgms.coredomain.domain.event.Event; @@ -84,7 +84,7 @@ void tearDown() { // When & Then assertThatThrownBy(() -> eventTimeService.createEventTime(event.getId(), request)) - .isInstanceOf(CustomException.class); + .isInstanceOf(EventException.class); } @Test diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventSeatStatus.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventSeatStatus.java index c2527af9..7319057b 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventSeatStatus.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventSeatStatus.java @@ -1,12 +1,24 @@ package com.pgms.coredomain.domain.event; -import lombok.RequiredArgsConstructor; +import lombok.Getter; -@RequiredArgsConstructor +@Getter public enum EventSeatStatus { AVAILABLE("예매가능"), BEING_BOOKED("예매중"), BOOKED("예매완료"); private final String description; + + EventSeatStatus(String description) { + this.description = description; + } + + public static EventSeatStatus of(String input) { + try { + return EventSeatStatus.valueOf(input.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException("존재하지 않는 좌석 상태입니다. : " + input); + } + } } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/GenreType.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/GenreType.java index 003e35ad..d7ff2028 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/GenreType.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/GenreType.java @@ -19,9 +19,9 @@ public enum GenreType { public static GenreType of(String input) { try { - return GenreType.valueOf(input); + return GenreType.valueOf(input.toUpperCase()); } catch (Exception e) { - throw e; + throw new IllegalArgumentException("존재하지 않는 장르입니다. : " + input); } } } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/SeatAreaType.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/SeatAreaType.java index 0d6b8240..c3a61089 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/SeatAreaType.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/SeatAreaType.java @@ -1,13 +1,23 @@ package com.pgms.coredomain.domain.event; import lombok.Getter; -import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor public enum SeatAreaType { R("R석"), S("S석"); private final String description; + + SeatAreaType(String description) { + this.description = description; + } + + public static SeatAreaType of(String input) { + try { + return SeatAreaType.valueOf(input.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException("존재하지 않는 좌석 구역 등급입니다. : " + input); + } + } } diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/buffer/DocumentBuffer.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/buffer/DocumentBuffer.java index 9acbad7d..61159c5d 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/buffer/DocumentBuffer.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/buffer/DocumentBuffer.java @@ -1,25 +1,25 @@ package com.pgms.coreinfraes.buffer; -import com.pgms.coreinfraes.document.EventDocument; - import java.util.Arrays; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import com.pgms.coreinfraes.document.EventDocument; + public class DocumentBuffer { - private static final Queue eventDocuments = new ConcurrentLinkedQueue<>(); + private static final Queue eventDocuments = new ConcurrentLinkedQueue<>(); - public static void add(EventDocument eventDocument) { - eventDocuments.add(eventDocument); - } + public static void add(EventDocument eventDocument) { + eventDocuments.add(eventDocument); + } - public static List getAll() { - return Arrays.asList(eventDocuments.toArray()); - } + public static List getAll() { + return Arrays.asList(eventDocuments.toArray()); + } - public static void deleteAll() { - eventDocuments.clear(); - } + public static void deleteAll() { + eventDocuments.clear(); + } } diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/config/ElasticSearchConfig.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/config/ElasticSearchConfig.java index 0d0987fb..44a7d3ed 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/config/ElasticSearchConfig.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/config/ElasticSearchConfig.java @@ -7,10 +7,10 @@ @Configuration public class ElasticSearchConfig extends ElasticsearchConfiguration { - @Override - public ClientConfiguration clientConfiguration() { - return ClientConfiguration.builder() - .connectedTo("localhost:9200") - .build(); - } + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() + .connectedTo("localhost:9200") + .build(); + } } diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/AccessLogDocument.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/AccessLogDocument.java index 1facf28e..692b721f 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/AccessLogDocument.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/AccessLogDocument.java @@ -1,12 +1,17 @@ package com.pgms.coreinfraes.document; -import jakarta.persistence.Id; -import lombok.*; +import java.time.LocalDateTime; + import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; -import java.time.LocalDateTime; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor @@ -15,15 +20,15 @@ @Document(indexName = "accessLog") public class AccessLogDocument { - @Id - private String id; + @Id + private String id; - @Field(name = "@timestamp",type = FieldType.Date) - private LocalDateTime date; + @Field(name = "@timestamp", type = FieldType.Date) + private LocalDateTime date; - @Field(type = FieldType.Keyword) - private String message; + @Field(type = FieldType.Keyword) + private String message; - @Field(name = "search_keyword", type = FieldType.Keyword) - private String searchKeyword; + @Field(name = "search_keyword", type = FieldType.Keyword) + private String searchKeyword; } diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/EventDocument.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/EventDocument.java index 669afef1..ca3b2983 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/EventDocument.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/document/EventDocument.java @@ -1,15 +1,24 @@ package com.pgms.coreinfraes.document; -import com.pgms.coredomain.domain.event.Event; -import com.pgms.coredomain.domain.event.GenreType; -import jakarta.persistence.Id; -import lombok.*; -import org.springframework.data.elasticsearch.annotations.*; +import static org.springframework.data.elasticsearch.annotations.DateFormat.*; import java.time.LocalDateTime; -import static org.springframework.data.elasticsearch.annotations.DateFormat.date_hour_minute_second; -import static org.springframework.data.elasticsearch.annotations.DateFormat.epoch_second; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.annotations.Setting; + +import com.pgms.coredomain.domain.event.Event; +import com.pgms.coredomain.domain.event.GenreType; + +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/EventDocumentResponse.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventDocumentResponse.java similarity index 93% rename from api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/EventDocumentResponse.java rename to core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventDocumentResponse.java index 10b5334b..b3ed5362 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/response/EventDocumentResponse.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventDocumentResponse.java @@ -1,4 +1,4 @@ -package com.pgms.apievent.eventSearch.dto.response; +package com.pgms.coreinfraes.dto; import com.pgms.coreinfraes.document.EventDocument; diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventKeywordSearchDto.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventKeywordSearchDto.java index 826e5157..17fd8ec3 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventKeywordSearchDto.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/dto/EventKeywordSearchDto.java @@ -1,9 +1,10 @@ package com.pgms.coreinfraes.dto; -import lombok.Builder; +import java.util.List; + import org.springframework.data.domain.Pageable; -import java.util.List; +import lombok.Builder; @Builder public record EventKeywordSearchDto( diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchQueryRepository.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchQueryRepository.java index 7fa8e6b5..37c1a01e 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchQueryRepository.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchQueryRepository.java @@ -1,37 +1,51 @@ package com.pgms.coreinfraes.repository; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregations; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; +import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.ScriptType; +import org.springframework.data.elasticsearch.core.SearchHitSupport; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; +import org.springframework.data.elasticsearch.core.query.ScriptData; +import org.springframework.data.elasticsearch.core.query.ScriptedField; +import org.springframework.data.elasticsearch.core.query.SourceFilter; +import org.springframework.data.elasticsearch.core.query.UpdateQuery; +import org.springframework.stereotype.Repository; + +import com.pgms.coreinfraes.document.AccessLogDocument; +import com.pgms.coreinfraes.document.EventDocument; +import com.pgms.coreinfraes.dto.EventDocumentResponse; +import com.pgms.coreinfraes.dto.EventKeywordSearchDto; +import com.pgms.coreinfraes.dto.TopTenSearchResponse; + import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.Script; import co.elastic.clients.elasticsearch._types.ScriptLanguage; import co.elastic.clients.elasticsearch._types.aggregations.Aggregation; import co.elastic.clients.elasticsearch._types.aggregations.AggregationBuilders; import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket; +import co.elastic.clients.elasticsearch._types.query_dsl.FieldValueFactorModifier; +import co.elastic.clients.elasticsearch._types.query_dsl.FieldValueFactorScoreFunction; +import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScore; import co.elastic.clients.elasticsearch._types.query_dsl.Query; -import co.elastic.clients.elasticsearch._types.query_dsl.*; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField; import co.elastic.clients.json.JsonData; -import com.pgms.coreinfraes.document.AccessLogDocument; -import com.pgms.coreinfraes.document.EventDocument; -import com.pgms.coreinfraes.dto.EventKeywordSearchDto; -import com.pgms.coreinfraes.dto.TopTenSearchResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregations; -import org.springframework.data.elasticsearch.client.elc.NativeQuery; -import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder; -import org.springframework.data.elasticsearch.core.*; -import org.springframework.data.elasticsearch.core.document.Document; -import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.core.query.*; -import org.springframework.stereotype.Repository; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; @Slf4j @Repository @@ -42,29 +56,40 @@ public class EventSearchQueryRepository { private final ElasticsearchOperations elasticsearchOperations; - public Page findByKeyword(EventKeywordSearchDto eventKeywordSearchDto) { + public Page findByKeyword(EventKeywordSearchDto eventKeywordSearchDto) { Pageable pageable = eventKeywordSearchDto.pageable(); NativeQuery query = getKeywordSearchNativeQuery(eventKeywordSearchDto).setPageable(pageable); SearchHits searchHits = elasticsearchOperations.search(query, EventDocument.class); log.info("event-keyword-search, {}", eventKeywordSearchDto.keyword()); - return SearchHitSupport.searchPageFor(searchHits, query.getPageable()).map(SearchHit::getContent); + return SearchHitSupport.searchPageFor(searchHits, query.getPageable()).map(s -> { + EventDocument eventDocument = s.getContent(); + return EventDocumentResponse.of(eventDocument); + }); } - public List getRecentTop10Keywords(){ + public List getRecentTop10Keywords() { NativeQuery searchQuery = getRecentTop10KeywordsNativeQuery(); - SearchHits searchHits = elasticsearchOperations.search(searchQuery, AccessLogDocument.class, IndexCoordinates.of("logstash*")); + SearchHits searchHits = elasticsearchOperations.search(searchQuery, AccessLogDocument.class, + IndexCoordinates.of("logstash*")); - ElasticsearchAggregations aggregations = (ElasticsearchAggregations) searchHits.getAggregations(); + ElasticsearchAggregations aggregations = (ElasticsearchAggregations)searchHits.getAggregations(); assert aggregations != null; - List topTenBuckets = aggregations.aggregationsAsMap().get("top_ten").aggregation().getAggregate().sterms().buckets().array(); + List topTenBuckets = aggregations.aggregationsAsMap() + .get("top_ten") + .aggregation() + .getAggregate() + .sterms() + .buckets() + .array(); List result = new ArrayList<>(); topTenBuckets.forEach(topTenBucket -> { - TopTenSearchResponse topTenSearchResponse = new TopTenSearchResponse(topTenBucket.key().stringValue(), topTenBucket.docCount()); + TopTenSearchResponse topTenSearchResponse = new TopTenSearchResponse(topTenBucket.key().stringValue(), + topTenBucket.docCount()); result.add(topTenSearchResponse); }); @@ -75,23 +100,27 @@ private NativeQuery getRecentTop10KeywordsNativeQuery() { NativeQueryBuilder queryBuilder = new NativeQueryBuilder(); Query matchQuery = QueryBuilders.match() - .field("message") - .query("event-keyword-search") - .build() - ._toQuery(); + .field("message") + .query("event-keyword-search") + .build() + ._toQuery(); Query loggerQuery = QueryBuilders.term() - .field("logger_name.keyword") - .value("com.pgms.coreinfraes.repository.EventSearchQueryRepository") - .build() - ._toQuery(); + .field("logger_name.keyword") + .value("com.pgms.coreinfraes.repository.EventSearchQueryRepository") + .build() + ._toQuery(); Query rangeQuery = QueryBuilders.range() - .field("@timestamp") - .gte(JsonData.of(LocalDateTime.now().truncatedTo(ChronoUnit.HOURS).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())) - .lte(JsonData.of(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())) - .build() - ._toQuery(); + .field("@timestamp") + .gte(JsonData.of(LocalDateTime.now() + .truncatedTo(ChronoUnit.HOURS) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli())) + .lte(JsonData.of(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())) + .build() + ._toQuery(); Script script = Script.of(scriptBuilder -> scriptBuilder.inline(inlineScriptBuilder -> inlineScriptBuilder.lang(ScriptLanguage.Painless) @@ -100,32 +129,31 @@ private NativeQuery getRecentTop10KeywordsNativeQuery() { )); Aggregation agg = AggregationBuilders.terms() - .script(script) - .size(10) - .build() - ._toAggregation(); - + .script(script) + .size(10) + .build() + ._toAggregation(); Query boolQuery = QueryBuilders.bool() - .must(matchQuery, loggerQuery, rangeQuery) - .build() - ._toQuery(); + .must(matchQuery, loggerQuery, rangeQuery) + .build() + ._toQuery(); ScriptedField scriptedField = new ScriptedField("search_keyword", new ScriptData( - ScriptType.INLINE, - "painless", - "return doc['message.keyword'].value.substring(22);", - "keyword_script", - Collections.emptyMap() + ScriptType.INLINE, + "painless", + "return doc['message.keyword'].value.substring(22);", + "keyword_script", + Collections.emptyMap() )); SourceFilter sourceFilter = new FetchSourceFilter(new String[] {"*"}, new String[] {}); return queryBuilder.withQuery(boolQuery) - .withSourceFilter(sourceFilter) - .withScriptedField(scriptedField) - .withAggregation("top_ten", agg) - .build(); + .withSourceFilter(sourceFilter) + .withScriptedField(scriptedField) + .withAggregation("top_ten", agg) + .build(); } private NativeQuery getKeywordSearchNativeQuery(EventKeywordSearchDto eventKeywordSearchDto) { diff --git a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchRepository.java b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchRepository.java index ea14d920..7ed20254 100644 --- a/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchRepository.java +++ b/core/core-infra-es/src/main/java/com/pgms/coreinfraes/repository/EventSearchRepository.java @@ -1,7 +1,8 @@ package com.pgms.coreinfraes.repository; -import com.pgms.coreinfraes.document.EventDocument; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import com.pgms.coreinfraes.document.EventDocument; + public interface EventSearchRepository extends ElasticsearchRepository { } From 5d52fd94620845fe04e37855fb887430c60eb418 Mon Sep 17 00:00:00 2001 From: zerozae <84398970+park0jae@users.noreply.github.com> Date: Tue, 9 Jan 2024 16:30:36 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20#175=20=EA=B3=B5=EC=97=B0=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20-=20=EB=9E=AD?= =?UTF-8?q?=ED=82=B9=EC=88=9C=20=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#176)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api-event/http/event.http | 10 ++++++---- .../apievent/event/dto/request/EventCreateRequest.java | 8 +++++++- .../apievent/event/dto/request/EventUpdateRequest.java | 2 ++ .../apievent/event/dto/response/EventResponse.java | 4 ++++ .../event/repository/EventCustomRepositoryImpl.java | 4 ++-- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/api/api-event/http/event.http b/api/api-event/http/event.http index 0a0db1e4..622db995 100644 --- a/api/api-event/http/event.http +++ b/api/api-event/http/event.http @@ -3,27 +3,29 @@ POST http://localhost:8080/api/v1/events Content-Type: application/json { - "title": "엘라스틱 서치", + "title": "엘라스틱 서치3", "description": "test", "runningTime": 100, "startedAt": "2024-01-05T12:00:00", "endedAt": "2024-01-06T12:00:00", "viewRating": "12", "genreType": "CONCERT", + "bookingStartedAt": "2024-01-05T12:00:00", + "bookingEndedAt": "2025-01-06T12:00:00", "eventHallId": 1 } ### 공연 아이디로 조회 -GET http://localhost:8080/api/v1/events/1 +GET http://localhost:8080/api/v1/events/2 ### 공연 목록 - 랭킹 GET http://localhost:8080/api/v1/events/sort/ranking?page=1&size=10&genreType=CONCERT&dateOffset=7 ### 공연 목록 - 리뷰순 -GET http://localhost:8080/api/v1/events/sort/review?page=1&size=10&genreType=CONCERT&dateOffset=7 +GET http://localhost:8080/api/v1/events/sort/review?page=1&size=10&genreType=CONCERT ### 공연 목록 - 예약마감일자순 -GET http://localhost:8080/api/v1/events/sort/ended-at?page=1&size=10&genreType=CONCERT&dateOffset=7 +GET http://localhost:8080/api/v1/events/sort/ended-at?page=1&size=10&genreType=CONCERT ### 공연 수정 PUT http://localhost:8080/api/v1/events/2 diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java index 233682ed..da5d2a74 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java @@ -33,9 +33,13 @@ public record EventCreateRequest( @NotNull(message = "공연 장르 타입은 필수 입력값 입니다.") GenreType genreType, + LocalDateTime bookingStartedAt, + + LocalDateTime bookingEndedAt, + @NotNull(message = "이벤트 홀 ID는 필수 입력값 입니다.") Long eventHallId) { - + public Event toEntity(EventHall eventHall) { return Event.builder() .title(title) @@ -46,6 +50,8 @@ public Event toEntity(EventHall eventHall) { .viewRating(viewRating) .genreType(genreType) .thumbnail("defaultThumbnail.jpg") + .bookingStartedAt(bookingStartedAt) + .bookingEndedAt(bookingEndedAt) .eventHall(eventHall) .build(); } diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java index a4423377..a1d9ca3c 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java @@ -23,8 +23,10 @@ @Positive(message = "공연 러닝 타임은 0보다 큰 값 이어야 합니다.") int runningTime, + @Nullable LocalDateTime startedAt, + @Nullable LocalDateTime endedAt, @NotBlank(message = "관람 등급은 필수 입력값 입니다.") diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/response/EventResponse.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/response/EventResponse.java index f637a021..a65c37af 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/response/EventResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/response/EventResponse.java @@ -12,6 +12,8 @@ public record EventResponse( String rating, String genreType, String thumbnail, + String bookingStartedAt, + String bookingEndedAt, String eventHall, Double averageScore ) { @@ -26,6 +28,8 @@ public static EventResponse of(Event event) { event.getViewRating(), event.getGenreType().getDescription(), event.getThumbnail(), + event.getBookingStartedAt().toString(), + event.getBookingEndedAt().toString(), event.getEventHall().getName(), event.getAverageScore() ); diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/repository/EventCustomRepositoryImpl.java b/api/api-event/src/main/java/com/pgms/apievent/event/repository/EventCustomRepositoryImpl.java index 33fc400c..add7e322 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/repository/EventCustomRepositoryImpl.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/repository/EventCustomRepositoryImpl.java @@ -39,11 +39,11 @@ public Page getEventsPageByGenreSortedByRanking(EventPageRequest .with(LocalTime.MIN); List content = jpaQueryFactory.selectFrom(event) - .join(eventTime) + .leftJoin(eventTime) .on(eventTime.event.eq(event)) .leftJoin(booking) .on(booking.time.eq(eventTime)) - .where(event.genreType.eq(genre), booking.createdAt.goe(cmpDate)) + .where(event.genreType.eq(genre), booking.createdAt.coalesce(cmpDate).goe(cmpDate)) .groupBy(event.id) .orderBy(booking.count().desc()) .offset(offset) From 2c3baf1bb20b171905891143c32cbb82c5df49f4 Mon Sep 17 00:00:00 2001 From: Hanna Lee <8annahxxl@gmail.com> Date: Wed, 10 Jan 2024 00:36:27 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EC=98=88=EB=A7=A4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 내 예매 목록 조회, 내 예매 상세 조회 기능 구현 * fix: LocalDateTime 응답 필드 String 타입으로 수정 * feat: 예매 상세 조회 seats 추가, querydsl fetchjoin 추가, condition 매핑 버그 수정 * fix: onetoone 관게 객체 querydsl에 fetch 조인 추가 * fix: 생성일 searchCondition 쿼리 버그 수정 --- api/api-booking/build.gradle | 6 ++ api/api-booking/http/booking.http | 9 ++ .../booking/controller/BookingController.java | 39 +++++++-- .../dto/request/BookingSearchCondition.java | 22 +++++ .../booking/dto/request/BookingSortType.java | 6 ++ .../booking/dto/request/DeliveryAddress.java | 16 ++++ .../booking/dto/request/PageCondition.java | 28 +++++++ .../dto/response/BookedSeatResponse.java | 18 ++++ .../dto/response/BookingCancelResponse.java | 10 +++ .../dto/response/BookingGetResponse.java | 66 +++++++++++++++ .../dto/response/BookingsGetResponse.java | 26 ++++++ .../booking/dto/response/PageResponse.java | 22 +++++ .../repository/BookingQuerydslRepository.java | 15 ++++ .../BookingQuerydslRepositoryImpl.java | 83 +++++++++++++++++++ .../booking/service/BookingService.java | 60 ++++++++++++-- .../dto/response/PaymentCardResponse.java | 10 +++ .../dto/response/PaymentVirtualResponse.java | 11 +++ .../coredomain/domain/booking/Booking.java | 4 + .../domain/booking/BookingStatus.java | 9 ++ .../domain/common/BookingErrorCode.java | 5 +- core/core-infra/src/main/resources/data.sql | 9 +- 21 files changed, 455 insertions(+), 19 deletions(-) create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSearchCondition.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSortType.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/PageCondition.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookedSeatResponse.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingCancelResponse.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingGetResponse.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingsGetResponse.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/PageResponse.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepository.java create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepositoryImpl.java diff --git a/api/api-booking/build.gradle b/api/api-booking/build.gradle index 30ee86a2..58ed5644 100644 --- a/api/api-booking/build.gradle +++ b/api/api-booking/build.gradle @@ -15,4 +15,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-security' + + // querydsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } diff --git a/api/api-booking/http/booking.http b/api/api-booking/http/booking.http index f857ed4f..ffa0e18c 100644 --- a/api/api-booking/http/booking.http +++ b/api/api-booking/http/booking.http @@ -53,8 +53,17 @@ Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0I ### 예매 취소 (브라우저에서 생성된 예매번호로 진행해 주세요) POST http://localhost:8082/api/v1/bookings/1704704631741/cancel +#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Content-Type: application/json { "cancelReason": "변심" } + +### 내 예매 내역 목록 조회 +GET http://localhost:8082/api/v1/bookings +#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ + +### 내 예매 내역 상세 조회 +GET http://localhost:8082/api/v1/bookings/bookingCancelTestId +#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java index d6a9eac3..1e7e4697 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java @@ -3,28 +3,34 @@ import java.net.URI; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; import com.pgms.apibooking.domain.booking.dto.request.BookingCancelRequest; import com.pgms.apibooking.domain.booking.dto.request.BookingCreateRequest; +import com.pgms.apibooking.domain.booking.dto.request.BookingSearchCondition; +import com.pgms.apibooking.domain.booking.dto.request.PageCondition; import com.pgms.apibooking.domain.booking.dto.response.BookingCreateResponse; +import com.pgms.apibooking.domain.booking.dto.response.BookingGetResponse; +import com.pgms.apibooking.domain.booking.dto.response.BookingsGetResponse; +import com.pgms.apibooking.domain.booking.dto.response.PageResponse; import com.pgms.apibooking.domain.booking.service.BookingService; import com.pgms.coredomain.response.ApiResponse; -import com.pgms.coresecurity.security.resolver.CurrentAccount; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -@Controller +@RestController @RequestMapping("/api/v1/bookings") @RequiredArgsConstructor -public class BookingController { //TODO: 인증된 멤버 연동 +public class BookingController { private final BookingService bookingService; @@ -33,7 +39,7 @@ public ResponseEntity> createBooking( //@CurrentAccount Long memberId, @RequestBody @Valid BookingCreateRequest request, HttpServletRequest httpRequest) { - BookingCreateResponse createdBooking = bookingService.createBooking(request, 1L); + BookingCreateResponse createdBooking = bookingService.createBooking(request, 1L); //TODO: 인증된 memberId 지정 ApiResponse response = ApiResponse.ok(createdBooking); URI location = UriComponentsBuilder .fromHttpUrl(httpRequest.getRequestURL().toString()) @@ -48,7 +54,7 @@ public ResponseEntity cancelBooking( //@CurrentAccount Long memberId, @PathVariable String id, @RequestBody @Valid BookingCancelRequest request) { - bookingService.cancelBooking(id, request, 1L); + bookingService.cancelBooking(id, request, 1L); //TODO: 인증된 memberId 지정 return ResponseEntity.ok().build(); } @@ -57,4 +63,25 @@ public ResponseEntity exitBooking(@PathVariable String id) { bookingService.exitBooking(id); return ResponseEntity.ok().build(); } + + @GetMapping + public ResponseEntity>> getBookings( + // @CurrentAccount Long memberId, + @ModelAttribute @Valid PageCondition pageCondition, + @ModelAttribute @Valid BookingSearchCondition searchCondition + ) { + PageResponse bookings = bookingService.getBookings(pageCondition, searchCondition, 1L); //TODO: 인증된 memberId 지정 + ApiResponse> response = ApiResponse.ok(bookings); + return ResponseEntity.ok().body(response); + } + + @GetMapping("/{id}") + public ResponseEntity> getBooking( + // @CurrentAccount Long memberId, + @PathVariable String id + ) { + BookingGetResponse booking = bookingService.getBooking(id, 1L); //TODO: 인증된 memberId 지정 + ApiResponse response = ApiResponse.ok(booking); + return ResponseEntity.ok().body(response); + } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSearchCondition.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSearchCondition.java new file mode 100644 index 00000000..1a5b837f --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSearchCondition.java @@ -0,0 +1,22 @@ +package com.pgms.apibooking.domain.booking.dto.request; + +import java.time.LocalDate; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class BookingSearchCondition { + + private Long memberId; + private String id; + private String status; + private LocalDate minCreatedAt; + private LocalDate maxCreatedAt; + private BookingSortType sortType; + + public void updateMemberId(Long memberId) { + this.memberId = memberId; + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSortType.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSortType.java new file mode 100644 index 00000000..aab00189 --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/BookingSortType.java @@ -0,0 +1,6 @@ +package com.pgms.apibooking.domain.booking.dto.request; + +public enum BookingSortType { + LATEST, + OLDEST; +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/DeliveryAddress.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/DeliveryAddress.java index b9dde4e8..03a7b039 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/DeliveryAddress.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/DeliveryAddress.java @@ -19,4 +19,20 @@ public record DeliveryAddress( @NotBlank(message = "[우편 번호] 입력은 필수입니다.") String zipCode ) { + + public static DeliveryAddress of( + String recipientName, + String recipientPhoneNumber, + String streetAddress, + String detailAddress, + String zipCode + ) { + return new DeliveryAddress( + recipientName, + recipientPhoneNumber, + streetAddress, + detailAddress, + zipCode + ); + } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/PageCondition.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/PageCondition.java new file mode 100644 index 00000000..14935b93 --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/request/PageCondition.java @@ -0,0 +1,28 @@ +package com.pgms.apibooking.domain.booking.dto.request; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PageCondition { + private static final Integer DEFAULT_PAGE = 1; + private static final Integer DEFAULT_SIZE = 10; + + private Integer page; + private Integer size; + + public PageCondition(Integer page, Integer size) { + this.page = isValidPage(page) ? page : DEFAULT_PAGE; + this.size = isValidSize(size) ? size : DEFAULT_SIZE; + } + + private Boolean isValidPage(Integer page) { + return page != null && page > 0; + } + + private Boolean isValidSize(Integer size) { + return size != null && size > 0; + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookedSeatResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookedSeatResponse.java new file mode 100644 index 00000000..d51b9a35 --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookedSeatResponse.java @@ -0,0 +1,18 @@ +package com.pgms.apibooking.domain.booking.dto.response; + +import com.pgms.coredomain.domain.event.EventSeat; + +public record BookedSeatResponse( + String areaType, + String name, + Integer price +) { + + public static BookedSeatResponse of(EventSeat seat) { + return new BookedSeatResponse( + seat.getEventSeatArea().getSeatAreaType().getDescription(), + seat.getName(), + seat.getEventSeatArea().getPrice() + ); + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingCancelResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingCancelResponse.java new file mode 100644 index 00000000..684b2b49 --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingCancelResponse.java @@ -0,0 +1,10 @@ +package com.pgms.apibooking.domain.booking.dto.response; + +import java.time.LocalDateTime; + +public record BookingCancelResponse( + String reason, + Integer amount, + LocalDateTime createdAt +) { +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingGetResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingGetResponse.java new file mode 100644 index 00000000..c42a11bc --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingGetResponse.java @@ -0,0 +1,66 @@ +package com.pgms.apibooking.domain.booking.dto.response; + +import java.util.List; + +import com.pgms.apibooking.domain.booking.dto.request.DeliveryAddress; +import com.pgms.apibooking.domain.payment.dto.response.PaymentCardResponse; +import com.pgms.apibooking.domain.payment.dto.response.PaymentVirtualResponse; +import com.pgms.coredomain.domain.booking.Booking; +import com.pgms.coredomain.domain.booking.PaymentMethod; +import com.pgms.coredomain.domain.booking.ReceiptType; + +public record BookingGetResponse( + String id, + Integer amount, + String status, + String buyerName, + String buyerPhoneNumber, + Long eventId, + String eventThumbnail, + String eventName, + Integer eventTime, + String eventTimeStartedAt, + String eventTimeEndedAt, + String receiptType, + DeliveryAddress deliveryAddress, + String createdAt, + String paymentMethod, + PaymentCardResponse paymentCard, + PaymentVirtualResponse paymentVirtual, + String paymentApprovedAt, + List seats +) { + + public static BookingGetResponse from(Booking booking) { + return new BookingGetResponse( + booking.getId(), + booking.getAmount(), + booking.getStatus().getDescription(), + booking.getBuyerName(), + booking.getBuyerPhoneNumber(), + booking.getTime().getEvent().getId(), + booking.getTime().getEvent().getThumbnail(), + booking.getTime().getEvent().getTitle(), + booking.getTime().getRound(), + booking.getTime().getStartedAt().toString(), + booking.getTime().getEndedAt().toString(), + booking.getReceiptType().getDescription(), + booking.getReceiptType() == ReceiptType.PICK_UP ? null : + DeliveryAddress.of( + booking.getRecipientName(), + booking.getRecipientPhoneNumber(), + booking.getStreetAddress(), + booking.getDetailAddress(), + booking.getZipCode() + ), + booking.getCreatedAt().toString(), + booking.getPayment().getMethod().getDescription(), + booking.getPayment().getMethod() == PaymentMethod.VIRTUAL_ACCOUNT ? null : + PaymentCardResponse.from(booking.getPayment()), + booking.getPayment().getMethod() == PaymentMethod.CARD ? null : + PaymentVirtualResponse.from(booking.getPayment()), + booking.getPayment().getApprovedAt().toString(), + booking.getTickets().stream().map(ticket -> BookedSeatResponse.of(ticket.getSeat())).toList() + ); + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingsGetResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingsGetResponse.java new file mode 100644 index 00000000..f93c1e9d --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/BookingsGetResponse.java @@ -0,0 +1,26 @@ +package com.pgms.apibooking.domain.booking.dto.response; + +import com.pgms.coredomain.domain.booking.Booking; + +public record BookingsGetResponse( + String id, + String bookingName, + Integer amount, + String status, + String eventTimeStartedAt, + String eventTimeEndedAt, + String createdAt +) { + + public static BookingsGetResponse from(Booking booking) { + return new BookingsGetResponse( + booking.getId(), + booking.getBookingName(), + booking.getAmount(), + booking.getStatus().getDescription(), + booking.getTime().getStartedAt().toString(), + booking.getTime().getEndedAt().toString(), + booking.getCreatedAt().toString() + ); + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/PageResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/PageResponse.java new file mode 100644 index 00000000..b658f45b --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/dto/response/PageResponse.java @@ -0,0 +1,22 @@ +package com.pgms.apibooking.domain.booking.dto.response; + +import java.util.List; + +import org.springframework.data.domain.Page; + +public record PageResponse( + Integer totalPages, + Integer currentPage, + Integer pageSize, + List content +) { + + public static PageResponse of(Page page) { + return new PageResponse<>( + page.getTotalPages(), + page.getPageable().getPageNumber() + 1, + page.getPageable().getPageSize(), + page.getContent() + ); + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepository.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepository.java new file mode 100644 index 00000000..48ef42df --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepository.java @@ -0,0 +1,15 @@ +package com.pgms.apibooking.domain.booking.repository; + +import java.util.List; + +import org.springframework.data.domain.Pageable; + +import com.pgms.apibooking.domain.booking.dto.request.BookingSearchCondition; +import com.pgms.coredomain.domain.booking.Booking; + +public interface BookingQuerydslRepository { + + List findAll(BookingSearchCondition condition, Pageable pageable); + + Long count(BookingSearchCondition condition); +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepositoryImpl.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepositoryImpl.java new file mode 100644 index 00000000..91241ee7 --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/repository/BookingQuerydslRepositoryImpl.java @@ -0,0 +1,83 @@ +package com.pgms.apibooking.domain.booking.repository; + +import static com.pgms.coredomain.domain.booking.QBooking.*; +import static com.pgms.coredomain.domain.booking.QPayment.*; +import static com.pgms.coredomain.domain.event.QEventTime.*; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import com.pgms.apibooking.domain.booking.dto.request.BookingSearchCondition; +import com.pgms.apibooking.domain.booking.dto.request.BookingSortType; +import com.pgms.coredomain.domain.booking.Booking; +import com.pgms.coredomain.domain.booking.BookingStatus; +import com.pgms.coredomain.domain.booking.PaymentStatus; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class BookingQuerydslRepositoryImpl implements BookingQuerydslRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAll(BookingSearchCondition condition, Pageable pageable) { + return queryFactory + .selectFrom(booking) + .join(booking.payment, payment).fetchJoin() + .join(booking.time, eventTime).fetchJoin() + .leftJoin(booking.cancel).fetchJoin() + .where(createFilterCondition(condition)) + .orderBy(createSortCondition(condition.getSortType())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + + @Override + public Long count(BookingSearchCondition condition) { + return queryFactory + .select(booking.count()) + .join(booking.payment, payment).fetchJoin() + .from(booking) + .where(createFilterCondition(condition)) + .fetchOne(); + } + + private BooleanExpression[] createFilterCondition(BookingSearchCondition condition) { + return new BooleanExpression[] { + booking.member.id.eq(condition.getMemberId()), + booking.payment.status.ne(PaymentStatus.READY), + condition.getId() == null ? null : booking.id.eq(condition.getId()), + condition.getStatus() == null ? null : booking.status.eq(BookingStatus.fromDescription(condition.getStatus())), + createdAtGoe(condition.getMinCreatedAt() == null ? null : condition.getMinCreatedAt()), + createdAtLoe(condition.getMaxCreatedAt() == null ? null : condition.getMaxCreatedAt()), + }; + } + + private BooleanExpression createdAtGoe(LocalDate date) { + return date == null ? null : booking.createdAt.goe(date.atStartOfDay()); + } + + private BooleanExpression createdAtLoe(LocalDate date) { + return date == null ? null : booking.createdAt.loe(date.atTime(23, 59, 59)); + } + + private OrderSpecifier createSortCondition(BookingSortType sortType) { + if (sortType == null) { + return booking.createdAt.desc(); + } + + return switch (sortType) { + case LATEST -> booking.createdAt.desc(); + case OLDEST -> booking.createdAt.asc(); + }; + } +} diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java index 5b432b38..5e845c19 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java @@ -2,9 +2,11 @@ import java.util.List; import java.util.NoSuchElementException; -import java.util.Objects; import java.util.Optional; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -17,8 +19,14 @@ import com.pgms.apibooking.config.TossPaymentConfig; import com.pgms.apibooking.domain.booking.dto.request.BookingCancelRequest; import com.pgms.apibooking.domain.booking.dto.request.BookingCreateRequest; +import com.pgms.apibooking.domain.booking.dto.request.BookingSearchCondition; import com.pgms.apibooking.domain.booking.dto.request.DeliveryAddress; +import com.pgms.apibooking.domain.booking.dto.request.PageCondition; import com.pgms.apibooking.domain.booking.dto.response.BookingCreateResponse; +import com.pgms.apibooking.domain.booking.dto.response.BookingGetResponse; +import com.pgms.apibooking.domain.booking.dto.response.BookingsGetResponse; +import com.pgms.apibooking.domain.booking.dto.response.PageResponse; +import com.pgms.apibooking.domain.booking.repository.BookingQuerydslRepository; import com.pgms.apibooking.domain.bookingqueue.repository.BookingQueueRepository; import com.pgms.apibooking.domain.payment.dto.request.PaymentCancelRequest; import com.pgms.apibooking.domain.payment.dto.request.RefundAccountRequest; @@ -51,6 +59,7 @@ public class BookingService { //TODO: 테스트 코드 작성 private final BookingRepository bookingRepository; private final TicketRepository ticketRepository; private final MemberRepository memberRepository; + private final BookingQuerydslRepository bookingQuerydslRepository; private final BookingQueueRepository bookingQueueRepository; private final TossPaymentConfig tossPaymentConfig; private final PaymentService paymentService; @@ -92,8 +101,8 @@ public void cancelBooking(String id, BookingCancelRequest request, Long memberId Booking booking = bookingRepository.findBookingInfoById(id) .orElseThrow(() -> new BookingException(BookingErrorCode.BOOKING_NOT_FOUND)); - if (!Objects.equals(member, booking.getMember())) { - throw new BookingException(BookingErrorCode.NOT_SAME_BOOKER); + if (!booking.isSameBooker(memberId)) { + throw new BookingException(BookingErrorCode.FORBIDDEN); } if (!booking.isCancelable()) { @@ -123,6 +132,46 @@ public void exitBooking(String id) { ticketsWithSeat.forEach(ticket -> ticket.getSeat().updateStatus(EventSeatStatus.AVAILABLE)); } + @Transactional(readOnly = true) + public PageResponse getBookings( + PageCondition pageCondition, + BookingSearchCondition searchCondition, + Long memberId + ) { + Member member = getMemberById(memberId); + + Pageable pageable = PageRequest.of(pageCondition.getPage() - 1, pageCondition.getSize()); + searchCondition.updateMemberId(member.getId()); + + List bookings = bookingQuerydslRepository.findAll(searchCondition, pageable) + .stream() + .map(BookingsGetResponse::from) + .toList(); + + return PageResponse.of( + PageableExecutionUtils.getPage(bookings, pageable, () -> bookingQuerydslRepository.count(searchCondition)) + ); + } + + @Transactional(readOnly = true) + public BookingGetResponse getBooking(String id, Long memberId) { + Member member = getMemberById(memberId); + + Booking booking = bookingRepository.findBookingInfoById(id) + .orElseThrow(() -> new BookingException(BookingErrorCode.BOOKING_NOT_FOUND)); + + if (!booking.isSameBooker(member.getId())) { + throw new BookingException(BookingErrorCode.FORBIDDEN); + } + + return BookingGetResponse.from(booking); + } + + @Async + protected void removeSessionIdInBookingQueue(Long eventId) { + bookingQueueRepository.remove(eventId, getCurrentSessionId()); + } + private EventTime getBookableTimeWithEvent(Long timeId) { EventTime time = eventTimeRepository.findWithEventById(timeId) .orElseThrow(() -> new BookingException(BookingErrorCode.TIME_NOT_FOUND)); @@ -176,9 +225,4 @@ private String getCurrentSessionId() { } return null; } - - @Async - protected void removeSessionIdInBookingQueue(Long eventId) { - bookingQueueRepository.remove(eventId, getCurrentSessionId()); - } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentCardResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentCardResponse.java index a2492830..47ef5086 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentCardResponse.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentCardResponse.java @@ -1,8 +1,18 @@ package com.pgms.apibooking.domain.payment.dto.response; +import com.pgms.coredomain.domain.booking.Payment; + public record PaymentCardResponse( String number, int installmentPlanMonths, boolean isInterestFree ) { + + public static PaymentCardResponse from(Payment payment) { + return new PaymentCardResponse( + payment.getCardNumber(), + payment.getInstallmentPlanMonths(), + payment.isInterestFree() + ); + } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentVirtualResponse.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentVirtualResponse.java index 8600f6f3..844ac8ab 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentVirtualResponse.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/payment/dto/response/PaymentVirtualResponse.java @@ -1,5 +1,7 @@ package com.pgms.apibooking.domain.payment.dto.response; +import com.pgms.coredomain.domain.booking.Payment; + public record PaymentVirtualResponse( // 토스 응답이라 이름변경x String accountNumber, @@ -7,4 +9,13 @@ public record PaymentVirtualResponse( String customerName, String dueDate ) { + + public static PaymentVirtualResponse from(Payment payment) { + return new PaymentVirtualResponse( + payment.getAccountNumber(), + payment.getBankCode().getBankNumCode(), + payment.getDepositorName(), + payment.getDueDate().toLocalDate().toString() + ); + } } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java index ff9eba3e..4fd971c3 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java @@ -156,4 +156,8 @@ public void cancel(BookingCancel cancel) { this.cancel = cancel; cancel.updateBooking(this); } + + public boolean isSameBooker(Long memberId) { + return this.member.getId().equals(memberId); + } } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/BookingStatus.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/BookingStatus.java index f26d7565..98a3510d 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/BookingStatus.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/BookingStatus.java @@ -12,4 +12,13 @@ public enum BookingStatus { CANCELED("취소"); private final String description; + + public static BookingStatus fromDescription(String description) { + for (BookingStatus bookingStatus : BookingStatus.values()) { + if (bookingStatus.description.equals(description)) { + return bookingStatus; + } + } + throw new IllegalArgumentException("다음 예매 상태를 찾을 수 없습니다 : " + description); + } } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java index fd2ecd0f..271a1b78 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java @@ -20,7 +20,6 @@ public enum BookingErrorCode implements BaseErrorCode{ UNBOOKABLE_SEAT_INCLUSION(HttpStatus.BAD_REQUEST, "UNBOOKABLE_SEAT_INCLUSION", "예매가 불가능한 좌석이 포함되어 있습니다."), DELIVERY_ADDRESS_REQUIRED(HttpStatus.BAD_REQUEST, "DELIVERY_ADDRESS_REQUIRED", "배송지 정보를 입력해주세요."), - NOT_SAME_BOOKER(HttpStatus.BAD_REQUEST, "NOT_SAME_BOOKER", "해당 예약의 예약자가 아닙니다."), UNCANCELABLE_BOOKING(HttpStatus.BAD_REQUEST, "UNCANCELABLE_BOOKING", "취소할 수 없는 예매입니다."), REFUND_ACCOUNT_REQUIRED(HttpStatus.BAD_REQUEST, "REFUND_ACCOUNT_REQUIRED", "환불 받을 계좌 정보를 입력해주세요."), @@ -38,7 +37,9 @@ public enum BookingErrorCode implements BaseErrorCode{ BOOKING_SESSION_ID_NOT_EXIST(HttpStatus.BAD_REQUEST, "BOOKING_SESSION_ID_NOT_EXIST", "예매 세션 ID가 존재하지 않습니다."), BOOKING_TOKEN_NOT_EXIST(HttpStatus.UNAUTHORIZED, "BOOKING_TOKEN_NOT_EXIST", "예매 토큰이 존재하지 않습니다."), INVALID_BOOKING_TOKEN(HttpStatus.UNAUTHORIZED, "INVALID_BOOKING_TOKEN", "올바르지 않은 예매 토큰입니다."), - OUT_OF_ORDER(HttpStatus.BAD_REQUEST, "OUT_OF_ORDER", "예매 순서가 아닙니다."); + OUT_OF_ORDER(HttpStatus.BAD_REQUEST, "OUT_OF_ORDER", "예매 순서가 아닙니다."), + + FORBIDDEN(HttpStatus.FORBIDDEN, "BOOKER_NOT_SAME", "권한이 없습니다."); private final HttpStatus status; private final String code; diff --git a/core/core-infra/src/main/resources/data.sql b/core/core-infra/src/main/resources/data.sql index b1db61d8..734d6aa0 100644 --- a/core/core-infra/src/main/resources/data.sql +++ b/core/core-infra/src/main/resources/data.sql @@ -61,7 +61,8 @@ INSERT INTO booking (id, status, receipt_type, member_id, - time_id) + time_id, + created_at) VALUES ('bookingCreateTestId', 180000, '빙봉의 주문', @@ -70,7 +71,8 @@ VALUES ('bookingCreateTestId', 'WAITING_FOR_PAYMENT', 'PICK_UP', 1, - 1), + 1, + CURRENT_TIMESTAMP), ('bookingCancelTestId', 180000, '주영의 주문', @@ -79,7 +81,8 @@ VALUES ('bookingCreateTestId', 'PAYMENT_COMPLETED', 'PICK_UP', 1, - 1); + 1, + CURRENT_TIMESTAMP); -- Ticket From 83a002973e36c78c5c1bdc8fb4559147089a634a Mon Sep 17 00:00:00 2001 From: zerozae <84398970+park0jae@users.noreply.github.com> Date: Wed, 10 Jan 2024 09:55:43 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EA=B3=B5=EC=97=B0=20=ED=9B=84?= =?UTF-8?q?=EA=B8=B0=20=EC=9E=91=EC=84=B1=EC=9E=90(=ED=9A=8C=EC=9B=90)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: #182 EventReview 작성자(회원) 추가 및 리뷰 작성, 수정, 삭제 로직에 회원 관련 로직 추가 * test: #182 EventReview 테스트 부분 MemberFactory로 Member 생성하도록 변경 --------- Co-authored-by: Kyunghun Kim <86098663+KarmaPol@users.noreply.github.com> --- .../controller/EventReviewController.java | 11 +++--- .../dto/request/EventReviewCreateRequest.java | 8 +++-- .../service/EventReviewService.java | 35 +++++++++++++------ .../service/EventReviewServiceTest.java | 35 ++++++++++++++----- .../eventreview/EventReviewFactory.java | 6 ++-- .../factory/member/MemberFactory.java | 20 +++++++++++ .../coredomain/domain/event/EventReview.java | 8 ++++- 7 files changed, 95 insertions(+), 28 deletions(-) create mode 100644 api/api-event/src/test/java/com/pgms/apievent/factory/member/MemberFactory.java diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java index 646db945..1cd9c295 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/controller/EventReviewController.java @@ -19,6 +19,7 @@ import com.pgms.apievent.eventreview.dto.response.EventReviewResponse; import com.pgms.apievent.eventreview.service.EventReviewService; import com.pgms.coredomain.response.ApiResponse; +import com.pgms.coresecurity.security.resolver.CurrentAccount; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -32,9 +33,10 @@ public class EventReviewController { @PostMapping("/{eventId}") public ResponseEntity createEventReview( + @CurrentAccount Long memberId, @PathVariable Long eventId, @Valid @RequestBody EventReviewCreateRequest request) { - EventReviewResponse response = eventReviewService.createEventReview(eventId, request); + EventReviewResponse response = eventReviewService.createEventReview(memberId, eventId, request); URI location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}") .buildAndExpand(response.id()) @@ -45,9 +47,10 @@ public ResponseEntity createEventReview( @PatchMapping("/{reviewId}") public ResponseEntity updateEventReview( + @CurrentAccount Long memberId, @PathVariable Long reviewId, @Valid @RequestBody EventReviewUpdateRequest request) { - EventReviewResponse response = eventReviewService.updateEventReview(reviewId, request); + EventReviewResponse response = eventReviewService.updateEventReview(memberId, reviewId, request); return ResponseEntity.ok(ApiResponse.ok(response)); } @@ -64,8 +67,8 @@ public ResponseEntity getEventReviewsForEventByEventId(@PathVariabl } @DeleteMapping("/{reviewId}") - public ResponseEntity deleteEventReviewById(@PathVariable Long reviewId) { - eventReviewService.deleteEventReviewById(reviewId); + public ResponseEntity deleteEventReviewById(@CurrentAccount Long memberId, @PathVariable Long reviewId) { + eventReviewService.deleteEventReviewById(memberId, reviewId); return ResponseEntity.noContent().build(); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java index 910d7b5d..a0403536 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/dto/request/EventReviewCreateRequest.java @@ -2,6 +2,7 @@ import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventReview; +import com.pgms.coredomain.domain.member.Member; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; @@ -12,12 +13,13 @@ public record EventReviewCreateRequest( @Size(max = 1000, message = "공연 리뷰 내용은 최대 1,000자까지 입력 가능 합니다.") String content) { - - public EventReview toEntity(Event event) { + + public EventReview toEntity(Event event, Member member) { return new EventReview( score, content, - event + event, + member ); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java index 0033126b..ec1265f6 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java @@ -15,6 +15,8 @@ import com.pgms.coredomain.domain.event.EventReview; import com.pgms.coredomain.domain.event.repository.EventRepository; import com.pgms.coredomain.domain.event.repository.EventReviewRepository; +import com.pgms.coredomain.domain.member.Member; +import com.pgms.coredomain.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -25,11 +27,13 @@ public class EventReviewService { private final EventReviewRepository eventReviewRepository; private final EventRepository eventRepository; + private final MemberRepository memberRepository; - public EventReviewResponse createEventReview(Long eventId, EventReviewCreateRequest request) { + public EventReviewResponse createEventReview(Long memberId, Long eventId, EventReviewCreateRequest request) { + Member member = getMember(memberId); Event event = eventRepository.findById(eventId). orElseThrow(() -> new EventException(EVENT_NOT_FOUND)); - EventReview eventReview = eventReviewRepository.save(request.toEntity(event)); + EventReview eventReview = eventReviewRepository.save(request.toEntity(event, member)); Double averageScore = eventReviewRepository.findAverageScoreByEvent(event.getId()); event.updateAverageScore(averageScore); @@ -37,17 +41,17 @@ public EventReviewResponse createEventReview(Long eventId, EventReviewCreateRequ return EventReviewResponse.of(eventReview); } - public EventReviewResponse updateEventReview(Long reviewId, EventReviewUpdateRequest request) { - EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); + public EventReviewResponse updateEventReview(Long memberId, Long reviewId, EventReviewUpdateRequest request) { + // TODO : 작성자와 현재 로그인한 사람이 일치하는지 검증 로직 필요 + Member member = getMember(memberId); + EventReview eventReview = getEventReview(reviewId); eventReview.updateEventReview(request.content()); return EventReviewResponse.of(eventReview); } @Transactional(readOnly = true) public EventReviewResponse getEventReviewById(Long reviewId) { - EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); + EventReview eventReview = getEventReview(reviewId); return EventReviewResponse.of(eventReview); } @@ -59,9 +63,20 @@ public List getEventReviewsForEventByEventId(Long eventId) .toList(); } - public void deleteEventReviewById(Long reviewId) { - EventReview eventReview = eventReviewRepository.findById(reviewId) - .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); + public void deleteEventReviewById(Long memberId, Long reviewId) { + // TODO : 작성자와 현재 로그인한 사람이 일치하는지 검증 로직 필요 + Member member = getMember(memberId); + EventReview eventReview = getEventReview(reviewId); eventReviewRepository.delete(eventReview); } + + private EventReview getEventReview(Long reviewId) { + return eventReviewRepository.findById(reviewId) + .orElseThrow(() -> new EventException(EVENT_REVIEW_NOT_FOUND)); + } + + private Member getMember(Long memberId) { + return memberRepository.findById(memberId) + .get(); + } } diff --git a/api/api-event/src/test/java/com/pgms/apievent/eventreview/service/EventReviewServiceTest.java b/api/api-event/src/test/java/com/pgms/apievent/eventreview/service/EventReviewServiceTest.java index 8385f6cd..08f115f4 100644 --- a/api/api-event/src/test/java/com/pgms/apievent/eventreview/service/EventReviewServiceTest.java +++ b/api/api-event/src/test/java/com/pgms/apievent/eventreview/service/EventReviewServiceTest.java @@ -18,12 +18,15 @@ import com.pgms.apievent.factory.event.EventFactory; import com.pgms.apievent.factory.eventhall.EventHallFactory; import com.pgms.apievent.factory.eventreview.EventReviewFactory; +import com.pgms.apievent.factory.member.MemberFactory; import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventHall; import com.pgms.coredomain.domain.event.EventReview; import com.pgms.coredomain.domain.event.repository.EventHallRepository; import com.pgms.coredomain.domain.event.repository.EventRepository; import com.pgms.coredomain.domain.event.repository.EventReviewRepository; +import com.pgms.coredomain.domain.member.Member; +import com.pgms.coredomain.domain.member.repository.MemberRepository; @SpringBootTest @ContextConfiguration(classes = EventTestConfig.class) @@ -44,6 +47,9 @@ class EventReviewServiceTest { @Autowired private EventHallRepository eventHallRepository; + @Autowired + private MemberRepository memberRepository; + @AfterEach void tearDown() { eventReviewRepository.deleteAll(); @@ -54,10 +60,12 @@ void tearDown() { // Given EventHall eventHall = eventHallRepository.save(EventHallFactory.createEventHall()); Event event = eventRepository.save(EventFactory.createEvent(eventHall)); + Member member = memberRepository.save(MemberFactory.createMember()); + EventReviewCreateRequest request = new EventReviewCreateRequest(5, "공연이 너무 재밌어요 !"); // When - EventReviewResponse response = eventReviewService.createEventReview(event.getId(), request); + EventReviewResponse response = eventReviewService.createEventReview(member.getId(), event.getId(), request); // Then assertThat(response.eventId()).isEqualTo(event.getId()); @@ -70,13 +78,15 @@ void tearDown() { // Given EventHall eventHall = eventHallRepository.save(EventHallFactory.createEventHall()); Event event = eventRepository.save(EventFactory.createEvent(eventHall)); + Member member = memberRepository.save(MemberFactory.createMember()); + List requestList = IntStream.range(0, REQUEST_NUMBER) .mapToObj(i -> new EventReviewCreateRequest(i, "리뷰 내용 " + i)) .toList(); // When IntStream.range(0, REQUEST_NUMBER) - .forEach(i -> eventReviewService.createEventReview(event.getId(), requestList.get(i))); + .forEach(i -> eventReviewService.createEventReview(member.getId(), event.getId(), requestList.get(i))); Event savedEvent = eventRepository.findById(event.getId()).get(); // Then @@ -88,12 +98,15 @@ void tearDown() { // Given EventHall eventHall = eventHallRepository.save(EventHallFactory.createEventHall()); Event event = eventRepository.save(EventFactory.createEvent(eventHall)); - EventReview eventReview = eventReviewRepository.save(EventReviewFactory.createEventReview(event)); + Member member = memberRepository.save(MemberFactory.createMember()); + + EventReview eventReview = eventReviewRepository.save(EventReviewFactory.createEventReview(event, member)); EventReviewUpdateRequest request = new EventReviewUpdateRequest("공연 후기 수정입니다~!"); // When - EventReviewResponse response = eventReviewService.updateEventReview(eventReview.getId(), request); + EventReviewResponse response = eventReviewService.updateEventReview(member.getId(), eventReview.getId(), + request); // Then assertThat(response.content()).isEqualTo(request.content()); @@ -104,7 +117,9 @@ void tearDown() { // Given EventHall eventHall = eventHallRepository.save(EventHallFactory.createEventHall()); Event event = eventRepository.save(EventFactory.createEvent(eventHall)); - EventReview eventReview = eventReviewRepository.save(EventReviewFactory.createEventReview(event)); + Member member = memberRepository.save(MemberFactory.createMember()); + + EventReview eventReview = eventReviewRepository.save(EventReviewFactory.createEventReview(event, member)); Long eventReviewId = eventReview.getId(); // When @@ -121,8 +136,10 @@ void tearDown() { // Given EventHall eventHall = eventHallRepository.save(EventHallFactory.createEventHall()); Event event = eventRepository.save(EventFactory.createEvent(eventHall)); + Member member = memberRepository.save(MemberFactory.createMember()); + IntStream.range(0, REQUEST_NUMBER) - .forEach(i -> eventReviewRepository.save(new EventReview(i, "리뷰 내용 " + i, event))); + .forEach(i -> eventReviewRepository.save(new EventReview(i, "리뷰 내용 " + i, event, member))); // When List responses = eventReviewService.getEventReviewsForEventByEventId(event.getId()); @@ -136,10 +153,12 @@ void tearDown() { // Given EventHall eventHall = eventHallRepository.save(EventHallFactory.createEventHall()); Event event = eventRepository.save(EventFactory.createEvent(eventHall)); - EventReview eventReview = eventReviewRepository.save(EventReviewFactory.createEventReview(event)); + Member member = memberRepository.save(MemberFactory.createMember()); + + EventReview eventReview = eventReviewRepository.save(EventReviewFactory.createEventReview(event, member)); // When - eventReviewService.deleteEventReviewById(eventReview.getId()); + eventReviewService.deleteEventReviewById(member.getId(), eventReview.getId()); // Then assertThat(eventReviewRepository.findAll()).hasSize(0); diff --git a/api/api-event/src/test/java/com/pgms/apievent/factory/eventreview/EventReviewFactory.java b/api/api-event/src/test/java/com/pgms/apievent/factory/eventreview/EventReviewFactory.java index 84daea9a..8c6749c1 100644 --- a/api/api-event/src/test/java/com/pgms/apievent/factory/eventreview/EventReviewFactory.java +++ b/api/api-event/src/test/java/com/pgms/apievent/factory/eventreview/EventReviewFactory.java @@ -2,14 +2,16 @@ import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventReview; +import com.pgms.coredomain.domain.member.Member; public class EventReviewFactory { - public static EventReview createEventReview(Event event) { + public static EventReview createEventReview(Event event, Member member) { return new EventReview( 4, "공연 후기입니다.", - event + event, + member ); } } diff --git a/api/api-event/src/test/java/com/pgms/apievent/factory/member/MemberFactory.java b/api/api-event/src/test/java/com/pgms/apievent/factory/member/MemberFactory.java new file mode 100644 index 00000000..96a56909 --- /dev/null +++ b/api/api-event/src/test/java/com/pgms/apievent/factory/member/MemberFactory.java @@ -0,0 +1,20 @@ +package com.pgms.apievent.factory.member; + +import com.pgms.coredomain.domain.member.Member; +import com.pgms.coredomain.domain.member.enums.Gender; + +public class MemberFactory { + public static Member createMember() { + return Member.builder() + .email("test@naver.com") + .password("encodedPassword") + .name("tester") + .phoneNumber("01011112222") + .birthDate("19990926") + .gender(Gender.MALE) + .streetAddress("서울특별시 송파구 올림픽로 300") + .detailAddress("롯데월드타워앤드롯데월드몰") + .zipCode("05551") + .build(); + } +} diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java index 6a4ac6b2..3020c93f 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java @@ -1,6 +1,7 @@ package com.pgms.coredomain.domain.event; import com.pgms.coredomain.domain.common.BaseEntity; +import com.pgms.coredomain.domain.member.Member; import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; @@ -40,10 +41,15 @@ public class EventReview extends BaseEntity { @JoinColumn(name = "event_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) private Event event; - public EventReview(Integer score, String content, Event event) { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + private Member member; + + public EventReview(Integer score, String content, Event event, Member member) { this.score = score; this.content = content; this.event = event; + this.member = member; } public void updateEventReview(String content) { From eef94536605bc44e84cd96dda1d19b6e56b03070 Mon Sep 17 00:00:00 2001 From: Kyunghun Kim <86098663+KarmaPol@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:33:40 +0900 Subject: [PATCH 05/10] =?UTF-8?q?refactor:=20=EA=B3=B5=EC=97=B0=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20validation=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(#186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: #183 dto validation 전체적으로 다듬기 * feat: #183 날짜 직렬화 양식 및 validation 추가 --- api/api-event/http/event-search.http | 2 +- api/api-event/http/event-time.http | 8 +++---- .../event/dto/request/EventCreateRequest.java | 15 ++++++++++--- .../event/dto/request/EventPageRequest.java | 4 +++- .../event/dto/request/EventUpdateRequest.java | 10 ++++++--- .../dto/request/EventHallCreateRequest.java | 6 ++--- .../dto/request/EventHallUpdateRequest.java | 6 ++--- .../controller/EventSearchController.java | 19 ++++++++-------- .../request/EventKeywordSearchRequest.java | 13 ++++++++--- .../controller/EventSeatAreaController.java | 22 ++++++------------- .../request/EventSeatAreaCreateRequest.java | 4 ++++ .../request/EventSeatAreaUpdateRequest.java | 7 +++++- .../dto/request/EventTimeCreateRequest.java | 16 ++++++++------ .../dto/request/EventTimeUpdateRequest.java | 8 ++++++- .../dto/response/EventTimeResponse.java | 8 +++---- .../eventtime/service/EventTimeService.java | 2 +- .../service/EventTimeServiceTest.java | 4 ++-- 17 files changed, 92 insertions(+), 62 deletions(-) diff --git a/api/api-event/http/event-search.http b/api/api-event/http/event-search.http index 38aaa136..4e1e2287 100644 --- a/api/api-event/http/event-search.http +++ b/api/api-event/http/event-search.http @@ -1,5 +1,5 @@ ### 공연 키워드 검색 -GET http://localhost:8080/api/v1/events/search/keyword?page=1&size=10&keyword=엘라스틱 서치2&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 diff --git a/api/api-event/http/event-time.http b/api/api-event/http/event-time.http index 75e971fe..b20203d2 100644 --- a/api/api-event/http/event-time.http +++ b/api/api-event/http/event-time.http @@ -5,8 +5,8 @@ Content-Type: application/json { "round": 1, - "startTime": "2024-01-05T12:00:00", - "endTime": "2024-01-05T12:00:00" + "startedAt": "2024-01-05T12:00:00", + "endedAt": "2024-01-05T12:00:00" } ### 회차 아이디로 회차 정보 조회 @@ -20,8 +20,8 @@ PATCH http://localhost:8080/api/v1/event-times/2 Content-Type: application/json { - "startTime": "2024-02-06T12:00:00", - "endTime": "2024-03-05T12:00:00" + "startedAt": "2024-02-06T12:00:00", + "endedAt": "2024-03-05T12:00:00" } ### 회차 아이디로 회차 삭제 diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java index da5d2a74..47edf3b2 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventCreateRequest.java @@ -1,15 +1,16 @@ package com.pgms.apievent.event.dto.request; -import java.time.LocalDateTime; - import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventHall; import com.pgms.coredomain.domain.event.GenreType; - +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; public record EventCreateRequest( @@ -23,8 +24,12 @@ public record EventCreateRequest( @Positive(message = "공연 러닝 타임은 0보다 큰 값 이어야 합니다.") int runningTime, + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime startedAt, + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime endedAt, @NotBlank(message = "관람 등급은 필수 입력값 입니다.") @@ -33,8 +38,12 @@ public record EventCreateRequest( @NotNull(message = "공연 장르 타입은 필수 입력값 입니다.") GenreType genreType, + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime bookingStartedAt, + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime bookingEndedAt, @NotNull(message = "이벤트 홀 ID는 필수 입력값 입니다.") diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventPageRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventPageRequest.java index 7cce1254..937c5617 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventPageRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventPageRequest.java @@ -1,12 +1,14 @@ package com.pgms.apievent.event.dto.request; import com.pgms.apievent.common.dto.request.PageRequestDto; - +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import lombok.Getter; @Getter public class EventPageRequest extends PageRequestDto { private String genreType; + @Min(1) @Max(10000) private Integer dateOffset; public EventPageRequest(Integer page, Integer size, String genreType, Integer dateOffset) { diff --git a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java index a1d9ca3c..65fc62e5 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/event/dto/request/EventUpdateRequest.java @@ -1,14 +1,14 @@ package com.pgms.apievent.event.dto.request; -import java.time.LocalDateTime; - import com.pgms.coredomain.domain.event.GenreType; - import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; public record EventUpdateRequest( @@ -24,9 +24,11 @@ int runningTime, @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime startedAt, @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime endedAt, @NotBlank(message = "관람 등급은 필수 입력값 입니다.") @@ -36,9 +38,11 @@ GenreType genreType, @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime bookingStartedAt, @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime bookingEndedAt, @NotNull(message = "이벤트 홀 ID는 필수 입력값 입니다.") diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java index 2cddf787..bbf8100f 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallCreateRequest.java @@ -1,13 +1,13 @@ package com.pgms.apievent.eventHall.dto.request; -import java.util.List; - import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.util.List; + public record EventHallCreateRequest( @NotBlank(message = "공연장 이름은 빈칸을 지정할 수 없습니다.") - @Size(max = 25, message = "공연장 이름은 10자 이내로 입력해주세요.") + @Size(max = 10, message = "공연장 이름은 10자 이내로 입력해주세요.") String name, String address, List eventHallSeatCreateRequests) { diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java index 2cd9680b..5cdfa3b0 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/dto/request/EventHallUpdateRequest.java @@ -1,13 +1,13 @@ package com.pgms.apievent.eventHall.dto.request; -import java.util.List; - import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.util.List; + public record EventHallUpdateRequest( @NotBlank(message = "공연장 이름은 빈칸을 지정할 수 없습니다.") - @Size(max = 25, message = "공연장 이름은 10자 이내로 입력해주세요.") + @Size(max = 10, message = "공연장 이름은 10자 이내로 입력해주세요.") String name, String address, List eventHallSeatCreateRequests) { diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java index fa030f26..a1c20b82 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/controller/EventSearchController.java @@ -1,20 +1,19 @@ package com.pgms.apievent.eventSearch.controller; -import java.util.List; - -import org.springframework.http.ResponseEntity; -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 com.pgms.apievent.common.dto.response.PageResponseDto; 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.coredomain.response.ApiResponse; - +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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; @RestController @RequestMapping("/api/v1/events/search") @@ -25,7 +24,7 @@ public class EventSearchController { @GetMapping("/keyword") public ResponseEntity searchEventsByKeyword( - @ModelAttribute EventKeywordSearchRequest eventKeywordSearchRequest) { + @ModelAttribute @Valid EventKeywordSearchRequest eventKeywordSearchRequest) { PageResponseDto response = eventSearchService.searchEventsByKeyword(eventKeywordSearchRequest); return ResponseEntity.ok(ApiResponse.ok(response)); } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java index 51ea15db..dc728f92 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSearch/dto/request/EventKeywordSearchRequest.java @@ -1,17 +1,24 @@ package com.pgms.apievent.eventSearch.dto.request; -import java.util.List; - import com.pgms.apievent.common.dto.request.PageRequestDto; import com.pgms.coreinfraes.dto.EventKeywordSearchDto; - +import jakarta.validation.constraints.Size; import lombok.Getter; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.annotation.Nullable; +import java.util.List; @Getter public class EventKeywordSearchRequest extends PageRequestDto { + @Size(min= 2,message = "검색어를 2글자 이상 입력해주세요.") private String keyword; private List genreType; + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private String startedAt; + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private String endedAt; public EventKeywordSearchRequest( diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java index 1589b0ae..72c79e3b 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/controller/EventSeatAreaController.java @@ -1,24 +1,16 @@ package com.pgms.apievent.eventSeatArea.controller; -import java.util.List; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import com.pgms.apievent.eventSeatArea.dto.request.EventSeatAreaCreateRequest; import com.pgms.apievent.eventSeatArea.dto.request.EventSeatAreaUpdateRequest; import com.pgms.apievent.eventSeatArea.dto.response.EventSeatAreaResponse; import com.pgms.apievent.eventSeatArea.service.EventSeatAreaService; import com.pgms.coredomain.response.ApiResponse; - +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -30,7 +22,7 @@ public class EventSeatAreaController { @PostMapping("/{id}/seat-area") public ResponseEntity createEventSeatArea( @PathVariable Long id, - @RequestBody EventSeatAreaCreateRequest request) { + @RequestBody @Valid EventSeatAreaCreateRequest request) { List response = eventSeatAreaService.createEventSeatArea(id, request); return ResponseEntity.ok(ApiResponse.ok(response)); } @@ -50,7 +42,7 @@ public ResponseEntity deleteEventSeatArea(@PathVariable Long areaId) { @PutMapping("/seat-area/{areaId}") public ResponseEntity updateEventSeatArea( @PathVariable Long areaId, - @RequestBody EventSeatAreaUpdateRequest request) { + @RequestBody @Valid EventSeatAreaUpdateRequest request) { eventSeatAreaService.updateEventSeatArea(areaId, request); return ResponseEntity.noContent().build(); } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java index a2083d6a..cb595c5c 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaCreateRequest.java @@ -1,9 +1,13 @@ package com.pgms.apievent.eventSeatArea.dto.request; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; + import java.util.List; public record EventSeatAreaCreateRequest( String seatAreaType, + @Min(0) @Max(1000000000) Integer price, List requests) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java index 4d0a0f5b..1d6b7acd 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventSeatArea/dto/request/EventSeatAreaUpdateRequest.java @@ -1,6 +1,11 @@ package com.pgms.apievent.eventSeatArea.dto.request; import com.pgms.coredomain.domain.event.SeatAreaType; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; -public record EventSeatAreaUpdateRequest(SeatAreaType seatAreaType, Integer price) { +public record EventSeatAreaUpdateRequest( + SeatAreaType seatAreaType, + @Min(0) @Max(1000000000) + Integer price) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java index e5410e3f..4cc94fce 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeCreateRequest.java @@ -1,24 +1,26 @@ package com.pgms.apievent.eventtime.dto.request; -import java.time.LocalDateTime; - import com.pgms.coredomain.domain.event.Event; import com.pgms.coredomain.domain.event.EventTime; - import jakarta.validation.constraints.Positive; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; public record EventTimeCreateRequest( @Positive(message = "회차는 0보다 큰 값 이어야 합니다.") int round, - LocalDateTime startTime, - LocalDateTime endTime) { + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + LocalDateTime startedAt, + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + LocalDateTime endedAt) { public EventTime toEntity(Event event) { return new EventTime( round, - startTime, - endTime, + startedAt, + endedAt, event); } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java index e08892a8..8f269328 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/request/EventTimeUpdateRequest.java @@ -1,6 +1,12 @@ package com.pgms.apievent.eventtime.dto.request; +import org.springframework.format.annotation.DateTimeFormat; + import java.time.LocalDateTime; -public record EventTimeUpdateRequest(LocalDateTime startTime, LocalDateTime endTime) { +public record EventTimeUpdateRequest( + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + LocalDateTime startedAt, + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + LocalDateTime endedAt) { } diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java index 5fd03a8a..10840c39 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/dto/response/EventTimeResponse.java @@ -1,15 +1,15 @@ package com.pgms.apievent.eventtime.dto.response; -import java.time.LocalDateTime; - import com.pgms.coredomain.domain.event.EventTime; +import java.time.LocalDateTime; + public record EventTimeResponse( Long id, Long eventId, int round, - LocalDateTime startTime, - LocalDateTime endTime) { + LocalDateTime startedAt, + LocalDateTime endedAt) { public static EventTimeResponse of(EventTime eventTime) { return new EventTimeResponse( diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java b/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java index 3fb6397d..e66890bb 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventtime/service/EventTimeService.java @@ -51,7 +51,7 @@ public EventTimeResponse updateEventTime(Long eventTimeId, EventTimeUpdateReques EventTime eventTime = eventTimeRepository.findById(eventTimeId) .orElseThrow(() -> new EventException(EVENT_TIME_NOT_FOUND)); - eventTime.updateEventTime(request.startTime(), request.endTime()); + eventTime.updateEventTime(request.startedAt(), request.endedAt()); return EventTimeResponse.of(eventTime); } diff --git a/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java b/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java index 2a4c194c..b4f77fbf 100644 --- a/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java +++ b/api/api-event/src/test/java/com/pgms/apievent/eventtime/service/EventTimeServiceTest.java @@ -147,8 +147,8 @@ void tearDown() { EventTimeResponse response = eventTimeService.updateEventTime(eventTime.getId(), request); // Then - assertThat(response.startTime()).isEqualTo(request.startTime()); - assertThat(response.endTime()).isEqualTo(request.endTime()); + assertThat(response.startedAt()).isEqualTo(request.startedAt()); + assertThat(response.endedAt()).isEqualTo(request.endedAt()); } @Test From faf1e2e7d2dbf76d1cf0b240c74b54846a91f1d8 Mon Sep 17 00:00:00 2001 From: Hanna Lee <8annahxxl@gmail.com> Date: Wed, 10 Jan 2024 10:42:31 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20seat=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A9=A4=EB=B2=84=20=EC=97=B0=EB=8F=99=20=EB=B0=8F?= =?UTF-8?q?=20booking=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?(#187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: byulcode --- .../common/exception/BookingException.java | 6 ++--- .../exception/BookingExceptionHandler.java | 13 ++++----- .../booking/controller/BookingController.java | 6 +++-- .../booking/service/BookingService.java | 17 +++++------- .../seat/controller/SeatController.java | 9 ++++--- .../domain/seat/service/SeatLockService.java | 14 ++++++++++ .../domain/seat/service/SeatService.java | 27 ++++++++++++------- .../domain/common/BookingErrorCode.java | 2 ++ 8 files changed, 57 insertions(+), 37 deletions(-) diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingException.java b/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingException.java index 7114b266..16eac5d6 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingException.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingException.java @@ -1,15 +1,15 @@ package com.pgms.apibooking.common.exception; -import com.pgms.coredomain.domain.common.BookingErrorCode; +import com.pgms.coredomain.domain.common.BaseErrorCode; import lombok.Getter; @Getter public class BookingException extends RuntimeException { - private final BookingErrorCode errorCode; + private final BaseErrorCode errorCode; - public BookingException(BookingErrorCode errorCode) { + public BookingException(BaseErrorCode errorCode) { super(errorCode.getMessage()); this.errorCode = errorCode; } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingExceptionHandler.java b/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingExceptionHandler.java index c9df8a07..785f6ad0 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingExceptionHandler.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/common/exception/BookingExceptionHandler.java @@ -28,8 +28,7 @@ public class BookingExceptionHandler extends ResponseEntityExceptionHandler { protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) { if (ex instanceof BindException) { - BookingErrorCode errorCode = BookingErrorCode.INVALID_INPUT_VALUE; - ErrorResponse response = new ErrorResponse(errorCode.getCode(), errorCode.getMessage()); + ErrorResponse response = BookingErrorCode.INVALID_INPUT_VALUE.getErrorResponse(); ((BindException)ex).getBindingResult().getAllErrors().forEach(e -> { String fieldName = ((FieldError)e).getField(); @@ -42,16 +41,14 @@ protected ResponseEntity handleExceptionInternal(Exception ex, Object bo log.error(ex.getMessage(), ex); - BookingErrorCode errorCode = BookingErrorCode.INTERNAL_SERVER_ERROR; - ErrorResponse response = new ErrorResponse(errorCode.getCode(), errorCode.getMessage()); + ErrorResponse response = BookingErrorCode.INTERNAL_SERVER_ERROR.getErrorResponse(); return ResponseEntity.internalServerError().body(response); } @Override protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { - BookingErrorCode errorCode = BookingErrorCode.INVALID_INPUT_VALUE; - ErrorResponse response = new ErrorResponse(errorCode.getCode(), errorCode.getMessage()); + ErrorResponse response = BookingErrorCode.INVALID_INPUT_VALUE.getErrorResponse(); return ResponseEntity.badRequest().body(response); } @@ -61,13 +58,13 @@ protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotV BindingResult bindingResult = ex.getBindingResult(); String errorMessage = Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage(); log.warn("Validation Failed: {}", errorMessage); - ErrorResponse response = new ErrorResponse(BookingErrorCode.INVALID_INPUT_VALUE.getCode(), errorMessage); + ErrorResponse response = BookingErrorCode.INVALID_INPUT_VALUE.getErrorResponse(); return ResponseEntity.status(status).body(response); } @ExceptionHandler(BookingException.class) protected ResponseEntity handleBookingException(BookingException ex) { - ErrorResponse response = new ErrorResponse(ex.getErrorCode().getCode(), ex.getErrorCode().getMessage()); + ErrorResponse response = ex.getErrorCode().getErrorResponse(); log.warn("Booking Exception Occurred : {}", response.getErrorMessage()); return ResponseEntity.status(ex.getErrorCode().getStatus()).body(response); } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java index 1e7e4697..20aa1687 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java @@ -38,7 +38,8 @@ public class BookingController { public ResponseEntity> createBooking( //@CurrentAccount Long memberId, @RequestBody @Valid BookingCreateRequest request, - HttpServletRequest httpRequest) { + HttpServletRequest httpRequest + ) { BookingCreateResponse createdBooking = bookingService.createBooking(request, 1L); //TODO: 인증된 memberId 지정 ApiResponse response = ApiResponse.ok(createdBooking); URI location = UriComponentsBuilder @@ -53,7 +54,8 @@ public ResponseEntity> createBooking( public ResponseEntity cancelBooking( //@CurrentAccount Long memberId, @PathVariable String id, - @RequestBody @Valid BookingCancelRequest request) { + @RequestBody @Valid BookingCancelRequest request + ) { bookingService.cancelBooking(id, request, 1L); //TODO: 인증된 memberId 지정 return ResponseEntity.ok().build(); } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java index 5e845c19..3593210c 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java @@ -1,7 +1,6 @@ package com.pgms.apibooking.domain.booking.service; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; import org.springframework.data.domain.PageRequest; @@ -13,9 +12,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.pgms.coredomain.domain.common.BookingErrorCode; import com.pgms.apibooking.common.exception.BookingException; -import com.pgms.coresecurity.security.jwt.booking.BookingAuthToken; import com.pgms.apibooking.config.TossPaymentConfig; import com.pgms.apibooking.domain.booking.dto.request.BookingCancelRequest; import com.pgms.apibooking.domain.booking.dto.request.BookingCreateRequest; @@ -39,6 +36,8 @@ import com.pgms.coredomain.domain.booking.Ticket; import com.pgms.coredomain.domain.booking.repository.BookingRepository; import com.pgms.coredomain.domain.booking.repository.TicketRepository; +import com.pgms.coredomain.domain.common.BookingErrorCode; +import com.pgms.coredomain.domain.common.MemberErrorCode; import com.pgms.coredomain.domain.event.EventSeat; import com.pgms.coredomain.domain.event.EventSeatStatus; import com.pgms.coredomain.domain.event.EventTime; @@ -46,6 +45,7 @@ import com.pgms.coredomain.domain.event.repository.EventTimeRepository; import com.pgms.coredomain.domain.member.Member; import com.pgms.coredomain.domain.member.repository.MemberRepository; +import com.pgms.coresecurity.security.jwt.booking.BookingAuthToken; import lombok.RequiredArgsConstructor; @@ -138,10 +138,8 @@ public PageResponse getBookings( BookingSearchCondition searchCondition, Long memberId ) { - Member member = getMemberById(memberId); - Pageable pageable = PageRequest.of(pageCondition.getPage() - 1, pageCondition.getSize()); - searchCondition.updateMemberId(member.getId()); + searchCondition.updateMemberId(memberId); List bookings = bookingQuerydslRepository.findAll(searchCondition, pageable) .stream() @@ -155,12 +153,10 @@ public PageResponse getBookings( @Transactional(readOnly = true) public BookingGetResponse getBooking(String id, Long memberId) { - Member member = getMemberById(memberId); - Booking booking = bookingRepository.findBookingInfoById(id) .orElseThrow(() -> new BookingException(BookingErrorCode.BOOKING_NOT_FOUND)); - if (!booking.isSameBooker(member.getId())) { + if (!booking.isSameBooker(memberId)) { throw new BookingException(BookingErrorCode.FORBIDDEN); } @@ -213,9 +209,8 @@ private void validateRefundReceiveAccount(PaymentMethod paymentMethod, } private Member getMemberById(Long memberId) { - System.out.println("member id get " + memberId); return memberRepository.findById(memberId) - .orElseThrow(() -> new NoSuchElementException("Member not found")); + .orElseThrow(() -> new BookingException(MemberErrorCode.MEMBER_NOT_FOUND)); } private String getCurrentSessionId() { diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/controller/SeatController.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/controller/SeatController.java index 602e939d..5f7b2e65 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/controller/SeatController.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/controller/SeatController.java @@ -14,6 +14,7 @@ import com.pgms.apibooking.domain.seat.dto.response.AreaResponse; import com.pgms.apibooking.domain.seat.service.SeatService; import com.pgms.coredomain.response.ApiResponse; +import com.pgms.coresecurity.security.resolver.CurrentAccount; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -32,14 +33,14 @@ public ResponseEntity>> getSeats(@ModelAttribute } @PostMapping("/{seatId}/select") - public ResponseEntity selectSeat(@PathVariable Long seatId) { - seatService.selectSeat(seatId); + public ResponseEntity selectSeat(@PathVariable Long seatId, @CurrentAccount Long memberId) { + seatService.selectSeat(seatId, memberId); return ResponseEntity.ok().build(); } @PostMapping("/{seatId}/deselect") - public ResponseEntity deselectSeat(@PathVariable Long seatId) { - seatService.deselectSeat(seatId); + public ResponseEntity deselectSeat(@PathVariable Long seatId, @CurrentAccount Long memberId) { + seatService.deselectSeat(seatId, memberId); return ResponseEntity.ok().build(); } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatLockService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatLockService.java index 9d9f22b3..5dd3b5d8 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatLockService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatLockService.java @@ -1,6 +1,7 @@ package com.pgms.apibooking.domain.seat.service; import java.time.Duration; +import java.util.Optional; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -17,6 +18,15 @@ class SeatLockService { //TODO: 레디스 레포지토리 분리 private final RedisTemplate redisTemplate; + Optional getSelectorId(Long seatId) { + String key = generateSeatLockKey(seatId); + String value = redisTemplate.opsForValue().get(key); + if (value == null) { + return Optional.empty(); + } + return Optional.of(extractMemberId(value)); + } + boolean isSeatLocked(Long seatId) { return redisTemplate.opsForValue().get(generateSeatLockKey(seatId)) != null; } @@ -39,4 +49,8 @@ private String generateSeatLockKey(Long seatId) { private String generateSeatLockValue(Long memberId) { return SEAT_LOCK_CACHE_VALUE_PREFIX + memberId; } + + private Long extractMemberId(String value) { + return Long.parseLong(value.replace(SEAT_LOCK_CACHE_VALUE_PREFIX, "")); + } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java index 525cd90e..a91a3dd5 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -37,9 +38,7 @@ public List getSeats(SeatsGetRequest request) { .toList(); } - public void selectSeat(Long seatId) { - Long memberId = 0L; //TODO: 인증된 memberId 지정 - + public void selectSeat(Long seatId, Long memberId) { if (seatLockService.isSeatLocked(seatId)) { throw new BookingException(BookingErrorCode.SEAT_BEING_BOOKED); } @@ -54,14 +53,19 @@ public void selectSeat(Long seatId) { seatLockService.lockSeat(seatId, memberId); } - public void deselectSeat(Long seatId) { - Long memberId = 0L; //TODO: 인증된 memberId 지정 - - //TODO: lock 걸어둔 멤버에게 요청이 들어왔는지 검증 후, 아래 로직을 수행한다 + public void deselectSeat(Long seatId, Long memberId) { + Optional selectorIdOpt = seatLockService.getSelectorId(seatId); - EventSeat seat = getSeat(seatId); + if(selectorIdOpt.isEmpty()) { + updateSeatStatusToAvailable(seatId); + throw new BookingException(BookingErrorCode.SEAT_SELECTION_EXPIRED); + } + + if (!selectorIdOpt.get().equals(memberId)) { + throw new BookingException(BookingErrorCode.SEAT_SELECTED_BY_ANOTHER_MEMBER); + } - seat.updateStatus(EventSeatStatus.AVAILABLE); + updateSeatStatusToAvailable(seatId); seatLockService.unlockSeat(seatId); } @@ -69,4 +73,9 @@ private EventSeat getSeat(Long seatId) { return eventSeatRepository.findById(seatId) .orElseThrow(() -> new BookingException(BookingErrorCode.SEAT_NOT_FOUND)); } + + private void updateSeatStatusToAvailable(Long seatId) { + EventSeat seat = getSeat(seatId); + seat.updateStatus(EventSeatStatus.AVAILABLE); + } } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java index 271a1b78..ca4168f2 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java @@ -13,6 +13,8 @@ public enum BookingErrorCode implements BaseErrorCode{ SEAT_NOT_FOUND(HttpStatus.BAD_REQUEST, "SEAT_NOT_FOUND", "존재하지 않는 좌석입니다."), SEAT_BEING_BOOKED(HttpStatus.BAD_REQUEST, "SEAT_BEING_BOOKED", "예매중인 좌석입니다."), SEAT_ALREADY_BOOKED(HttpStatus.BAD_REQUEST, "SEAT_ALREADY_BOOKED", "예매된 좌석입니다."), + SEAT_SELECTED_BY_ANOTHER_MEMBER(HttpStatus.BAD_REQUEST, "SEAT_SELECTED_BY_ANOTHER_MEMBER", "다른 회원이 선택한 좌석입니다."), + SEAT_SELECTION_EXPIRED(HttpStatus.BAD_REQUEST, "SEAT_SELECTION_EXPIRED", "좌석 선택 시간이 만료되었습니다."), TIME_NOT_FOUND(HttpStatus.BAD_REQUEST, "TIME_NOT_FOUND", "존재하지 않는 공연 회차입니다."), UNBOOKABLE_EVENT(HttpStatus.BAD_REQUEST, "UNBOOKABLE_EVENT", "현재 예매가 불가능한 공연입니다."), From 27b920eee277bb1e5823c9e80946db5a0e0c2984 Mon Sep 17 00:00:00 2001 From: Kyunghun Kim <86098663+KarmaPol@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:21:56 +0900 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=EA=B3=B5=EC=97=B0=EC=9E=A5=20?= =?UTF-8?q?=EC=A2=8C=EC=84=9D=20=EC=9D=BD=EC=96=B4=EC=98=A4=EC=A7=80=20?= =?UTF-8?q?=EB=AA=BB=ED=95=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20=EA=B3=B5=EC=97=B0=EC=9E=A5=20=EC=A2=8C=EC=84=9D-?= =?UTF-8?q?=EA=B3=B5=EC=97=B0=EC=9E=A5=20=EC=97=B0=EA=B4=80=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=EC=84=A4=EC=A0=95=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 공연장 좌석 읽어오지 못하는 오류 수정, 공연장 좌석-공연장 연관관계 설정 메소드 추가 * fix: #189 공연장 좌석 없을 경우 null 체크 조건 추가 --- .../eventHall/service/EventHallService.java | 15 ++++++--------- .../pgms/coredomain/domain/event/EventHall.java | 6 ++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java b/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java index 3a3efdb7..747e7bb7 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventHall/service/EventHallService.java @@ -1,12 +1,5 @@ package com.pgms.apievent.eventHall.service; -import static com.pgms.apievent.exception.EventErrorCode.*; - -import java.util.List; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.pgms.apievent.eventHall.dto.request.EventHallCreateRequest; import com.pgms.apievent.eventHall.dto.request.EventHallSeatCreateRequest; import com.pgms.apievent.eventHall.dto.request.EventHallUpdateRequest; @@ -17,8 +10,13 @@ import com.pgms.coredomain.domain.event.EventHallEdit; import com.pgms.coredomain.domain.event.EventHallSeat; import com.pgms.coredomain.domain.event.repository.EventHallRepository; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.pgms.apievent.exception.EventErrorCode.EVENT_HALL_NOT_FOUND; @Service @Transactional @@ -88,7 +86,6 @@ public EventHallResponse getEventHall(Long id) { EventHall eventHall = eventHallRepository.findById(id) .orElseThrow(() -> new EventException(EVENT_HALL_NOT_FOUND)); - // TODO 못 읽어옴 List eventHallSeatResponses = eventHall.getEventHallSeats() .stream() .map(EventHallSeatResponse::of) diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventHall.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventHall.java index cebfe218..1968a63b 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventHall.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventHall.java @@ -33,6 +33,12 @@ public EventHall(String name, String address, List eventHallSeats this.name = name; this.address = address; this.eventHallSeats = eventHallSeats; + setEventHallSeatsEventHall(); + } + + public void setEventHallSeatsEventHall(){ + if(this.eventHallSeats == null) return; + this.eventHallSeats.forEach(eventHallSeat -> eventHallSeat.setEventHall(this)); } public void updateEventHall(EventHallEdit eventHallEdit){ From ec66ccfb5f19e96d4ededf14bb9638486a0844c7 Mon Sep 17 00:00:00 2001 From: zerozae <84398970+park0jae@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:36:06 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20#188=20=EA=B3=B5=EC=97=B0=20?= =?UTF-8?q?=ED=9B=84=EA=B8=B0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=EC=9E=90(=ED=9A=8C=EC=9B=90)=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20(#192)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kyunghun Kim <86098663+KarmaPol@users.noreply.github.com> --- .../eventreview/service/EventReviewService.java | 10 ++++++++-- .../com/pgms/apievent/exception/EventErrorCode.java | 1 + .../com/pgms/coredomain/domain/event/EventReview.java | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java b/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java index ec1265f6..26102e69 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java +++ b/api/api-event/src/main/java/com/pgms/apievent/eventreview/service/EventReviewService.java @@ -42,9 +42,9 @@ public EventReviewResponse createEventReview(Long memberId, Long eventId, EventR } public EventReviewResponse updateEventReview(Long memberId, Long reviewId, EventReviewUpdateRequest request) { - // TODO : 작성자와 현재 로그인한 사람이 일치하는지 검증 로직 필요 Member member = getMember(memberId); EventReview eventReview = getEventReview(reviewId); + validateReviewer(eventReview, member); eventReview.updateEventReview(request.content()); return EventReviewResponse.of(eventReview); } @@ -64,9 +64,9 @@ public List getEventReviewsForEventByEventId(Long eventId) } public void deleteEventReviewById(Long memberId, Long reviewId) { - // TODO : 작성자와 현재 로그인한 사람이 일치하는지 검증 로직 필요 Member member = getMember(memberId); EventReview eventReview = getEventReview(reviewId); + validateReviewer(eventReview, member); eventReviewRepository.delete(eventReview); } @@ -79,4 +79,10 @@ private Member getMember(Long memberId) { return memberRepository.findById(memberId) .get(); } + + private void validateReviewer(EventReview eventReview, Member member) { + if (!eventReview.isSameReviewer(member)) { + throw new EventException(REVIEWER_MISMATCH_EXCEPTION); + } + } } diff --git a/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java b/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java index 80c30e15..005b154d 100644 --- a/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java +++ b/api/api-event/src/main/java/com/pgms/apievent/exception/EventErrorCode.java @@ -17,6 +17,7 @@ public enum EventErrorCode implements BaseErrorCode { EVENT_TIME_NOT_FOUND("EVENT TIME NOT FOUND", HttpStatus.NOT_FOUND, "존재하지 않는 회차입니다."), ALREADY_EXIST_EVENT_TIME("EVENT TIME ALREADY EXISTS", HttpStatus.CONFLICT, "공연에 대한 회차가 이미 존재합니다."), VALIDATION_FAILED("VALIDATION FAILED", HttpStatus.BAD_REQUEST, "입력값에 대한 검증에 실패했습니다."), + 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에 파일 업로드를 실패했습니다."); diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java index 3020c93f..1654f3e8 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/event/EventReview.java @@ -55,4 +55,11 @@ public EventReview(Integer score, String content, Event event, Member member) { public void updateEventReview(String content) { this.content = content; } + + public boolean isSameReviewer(Member member) { + if (member != null) { + return this.member.equals(member); + } + return false; + } } From c21479b77a34ea2bb6de8b44655bc39f4998bb7d Mon Sep 17 00:00:00 2001 From: byulcode Date: Wed, 10 Jan 2024 11:56:40 +0900 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20=EC=98=88=EB=A7=A4=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EA=B4=80=EB=A6=AC=20security=20filter=20-?= =?UTF-8?q?>=20interceptor=EB=A1=9C=20=EC=88=98=EC=A0=95=20&=20redis=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20Booking/180=20(#193)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: core-security에 예매 관련 로직 제거 & 예매 토큰 로직 booking-api로 옮기기 * feat: 예매 토큰 인터셉터 구현 * refactor: BookingTokenInterceptor 적용 * test: 인터셉터 로직으로 테스트코드 수정 * refactor: 멤버 연동 & 예매 생성 엔드포인트 변경 * feat: redis 대기열 예외 처리 추가 * refactor: redis 예외 처리 service단으로 수정 --- api/api-booking/build.gradle | 4 ++ api/api-booking/http/booking.http | 22 ++++---- .../interceptor/BookingTokenInterceptor.java | 45 ++++++++++++++++ .../common/jwt}/BookingJwtPayload.java | 2 +- .../common/jwt}/BookingJwtProvider.java | 2 +- .../apibooking}/config/BookingJwtConfig.java | 6 +-- .../com/pgms/apibooking/config/WebConfig.java | 8 +++ .../booking/controller/BookingController.java | 27 +++++----- .../booking/service/BookingService.java | 19 ++----- .../repository/BookingQueueRepository.java | 6 ++- .../service/BookingQueueService.java | 15 ++++-- .../src/main/resources/application.yml | 5 ++ .../service/BookingServiceTest.java | 11 ++-- .../domain/common/BookingErrorCode.java | 1 + .../jwt/booking/BookingAuthToken.java | 24 --------- .../BookingExceptionHandlerFilter.java | 47 ---------------- .../jwt/booking/BookingJwtAuthFilter.java | 53 ------------------- .../main/resources/application-security.yml | 4 -- 18 files changed, 117 insertions(+), 184 deletions(-) create mode 100644 api/api-booking/src/main/java/com/pgms/apibooking/common/interceptor/BookingTokenInterceptor.java rename {core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking => api/api-booking/src/main/java/com/pgms/apibooking/common/jwt}/BookingJwtPayload.java (80%) rename {core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking => api/api-booking/src/main/java/com/pgms/apibooking/common/jwt}/BookingJwtProvider.java (94%) rename {core/core-security/src/main/java/com/pgms/coresecurity/security => api/api-booking/src/main/java/com/pgms/apibooking}/config/BookingJwtConfig.java (84%) delete mode 100644 core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingAuthToken.java delete mode 100644 core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingExceptionHandlerFilter.java delete mode 100644 core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtAuthFilter.java diff --git a/api/api-booking/build.gradle b/api/api-booking/build.gradle index 58ed5644..366db693 100644 --- a/api/api-booking/build.gradle +++ b/api/api-booking/build.gradle @@ -16,6 +16,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + implementation 'io.jsonwebtoken:jjwt-impl:0.12.3' + implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3' + // querydsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" diff --git a/api/api-booking/http/booking.http b/api/api-booking/http/booking.http index ffa0e18c..31676658 100644 --- a/api/api-booking/http/booking.http +++ b/api/api-booking/http/booking.http @@ -1,11 +1,11 @@ ### 세션 아이디 발급 POST http://localhost:8082/api/v1/bookings/issue-session-id -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ ### 대기열 진입 POST http://localhost:8082/api/v1/bookings/enter-queue Content-Type: application/json -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Booking-Session-Id: {세션아이디} { @@ -14,14 +14,14 @@ Booking-Session-Id: {세션아이디} ### 대기열 조회 GET http://localhost:8082/api/v1/bookings/order-in-queue?eventId=1 -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Booking-Session-Id: {세션아이디} ### 예매 토큰 발급 POST http://localhost:8082/api/v1/bookings/issue-token Content-Type: application/json -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Booking-Session-Id: {세션아이디} { @@ -31,7 +31,7 @@ Booking-Session-Id: {세션아이디} ### [Optional] 대기열 이탈 POST http://localhost:8082/api/v1/bookings/exit-queue Content-Type: application/json -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Booking-Session-Id: {세션아이디} { @@ -40,20 +40,20 @@ Booking-Session-Id: {세션아이디} ### 좌석 목록 조회 GET http://localhost:8082/api/v1/seats?eventTimeId=1 -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB ### 예매 ~ 결제 (브라우저에서 진행해 주세요) -GET http://localhost:8082/bookings +GET http://localhost:8082/bookings/create ### [Optional] 예매 이탈 POST http://localhost:8082/api/v1/bookings/bookingCreateTestId/exit -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Booking-Authorization: Bearer eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJib29raW5nIiwiaWF0IjoxNzA0NzA0NTY0LCJleHAiOjQyMDAxNzA0NzA0NTY0LCJzZXNzaW9uSWQiOiJ7Pz8_Pz99In0.iKZaud5vfvsMzXmQzl1WaCweL9GL00U0iGzCg4_p3Zzei8Y7z19Ff_TTpxh9gLeB ### 예매 취소 (브라우저에서 생성된 예매번호로 진행해 주세요) POST http://localhost:8082/api/v1/bookings/1704704631741/cancel -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ Content-Type: application/json { @@ -62,8 +62,8 @@ Content-Type: application/json ### 내 예매 내역 목록 조회 GET http://localhost:8082/api/v1/bookings -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ ### 내 예매 내역 상세 조회 GET http://localhost:8082/api/v1/bookings/bookingCancelTestId -#Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDQ2OTE0OTEsImV4cCI6MTgwNDY4MzI5MSwiYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn0.wFNSz2uwRa35jP1KihNlTOewVLgMMeg3ADQ5Kztl3QQ diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/common/interceptor/BookingTokenInterceptor.java b/api/api-booking/src/main/java/com/pgms/apibooking/common/interceptor/BookingTokenInterceptor.java new file mode 100644 index 00000000..afd56713 --- /dev/null +++ b/api/api-booking/src/main/java/com/pgms/apibooking/common/interceptor/BookingTokenInterceptor.java @@ -0,0 +1,45 @@ +package com.pgms.apibooking.common.interceptor; + +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import com.pgms.apibooking.common.exception.BookingException; +import com.pgms.apibooking.common.jwt.BookingJwtPayload; +import com.pgms.apibooking.common.jwt.BookingJwtProvider; +import com.pgms.coredomain.domain.common.BookingErrorCode; + +import io.jsonwebtoken.JwtException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +@RequiredArgsConstructor +public class BookingTokenInterceptor implements HandlerInterceptor { + + private final BookingJwtProvider bookingJwtProvider; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws + Exception { + String token = parseToken(request); + try { + BookingJwtPayload authentication = bookingJwtProvider.validateAndParsePayload(token); + request.setAttribute("tokenSessionId", authentication.sessionId()); + } catch (JwtException | BookingException e) { + log.warn(e.getMessage(), e); + throw new BookingException(BookingErrorCode.INVALID_BOOKING_TOKEN); + } + return true; + } + + private String parseToken(HttpServletRequest request) { + String headerAuth = request.getHeader("Booking-Authorization"); + if (headerAuth != null && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7); + } + throw new BookingException(BookingErrorCode.BOOKING_TOKEN_NOT_EXIST); + } +} diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtPayload.java b/api/api-booking/src/main/java/com/pgms/apibooking/common/jwt/BookingJwtPayload.java similarity index 80% rename from core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtPayload.java rename to api/api-booking/src/main/java/com/pgms/apibooking/common/jwt/BookingJwtPayload.java index 1666179e..8d168b8c 100644 --- a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtPayload.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/common/jwt/BookingJwtPayload.java @@ -1,4 +1,4 @@ -package com.pgms.coresecurity.security.jwt.booking; +package com.pgms.apibooking.common.jwt; import java.util.HashMap; import java.util.Map; diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtProvider.java b/api/api-booking/src/main/java/com/pgms/apibooking/common/jwt/BookingJwtProvider.java similarity index 94% rename from core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtProvider.java rename to api/api-booking/src/main/java/com/pgms/apibooking/common/jwt/BookingJwtProvider.java index 278cbd2c..68dd6fe8 100644 --- a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtProvider.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/common/jwt/BookingJwtProvider.java @@ -1,4 +1,4 @@ -package com.pgms.coresecurity.security.jwt.booking; +package com.pgms.apibooking.common.jwt; import java.util.Date; diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/security/config/BookingJwtConfig.java b/api/api-booking/src/main/java/com/pgms/apibooking/config/BookingJwtConfig.java similarity index 84% rename from core/core-security/src/main/java/com/pgms/coresecurity/security/config/BookingJwtConfig.java rename to api/api-booking/src/main/java/com/pgms/apibooking/config/BookingJwtConfig.java index 0d452ca4..bc2d8578 100644 --- a/core/core-security/src/main/java/com/pgms/coresecurity/security/config/BookingJwtConfig.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/config/BookingJwtConfig.java @@ -1,12 +1,12 @@ -package com.pgms.coresecurity.security.config; +package com.pgms.apibooking.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import com.pgms.coresecurity.security.jwt.booking.BookingJwtProvider; - +import com.pgms.apibooking.common.jwt.BookingJwtProvider; import io.jsonwebtoken.security.Keys; + import lombok.Getter; @Getter diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/config/WebConfig.java b/api/api-booking/src/main/java/com/pgms/apibooking/config/WebConfig.java index dca5691c..448b07e2 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/config/WebConfig.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/config/WebConfig.java @@ -6,6 +6,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.pgms.apibooking.common.interceptor.BookingSessionInterceptor; +import com.pgms.apibooking.common.interceptor.BookingTokenInterceptor; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ public class WebConfig implements WebMvcConfigurer { private final BookingSessionInterceptor bookingSessionInterceptor; + private final BookingTokenInterceptor bookingTokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { @@ -23,5 +25,11 @@ public void addInterceptors(InterceptorRegistry registry) { .addPathPatterns("/api/*/bookings/order-in-queue") .addPathPatterns("/api/*/bookings/issue-token") .addPathPatterns("/api/*/bookings/exit-queue"); + registry.addInterceptor(bookingTokenInterceptor) + .addPathPatterns("/api/*/bookings/create") + .addPathPatterns("/api/*/exit") + .addPathPatterns("/api/*/seats") + .addPathPatterns("/api/*/*/select") + .addPathPatterns("/api/*/*/deselect"); } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java index 20aa1687..f36f33fe 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/controller/BookingController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -22,6 +23,7 @@ import com.pgms.apibooking.domain.booking.dto.response.PageResponse; import com.pgms.apibooking.domain.booking.service.BookingService; import com.pgms.coredomain.response.ApiResponse; +import com.pgms.coresecurity.security.resolver.CurrentAccount; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; @@ -34,13 +36,13 @@ public class BookingController { private final BookingService bookingService; - @PostMapping + @PostMapping("/create") public ResponseEntity> createBooking( - //@CurrentAccount Long memberId, + @CurrentAccount Long memberId, @RequestBody @Valid BookingCreateRequest request, - HttpServletRequest httpRequest - ) { - BookingCreateResponse createdBooking = bookingService.createBooking(request, 1L); //TODO: 인증된 memberId 지정 + @RequestAttribute("tokenSessionId") String tokenSessionId, + HttpServletRequest httpRequest) { + BookingCreateResponse createdBooking = bookingService.createBooking(request, memberId, tokenSessionId); ApiResponse response = ApiResponse.ok(createdBooking); URI location = UriComponentsBuilder .fromHttpUrl(httpRequest.getRequestURL().toString()) @@ -52,11 +54,10 @@ public ResponseEntity> createBooking( @PostMapping("/{id}/cancel") public ResponseEntity cancelBooking( - //@CurrentAccount Long memberId, + @CurrentAccount Long memberId, @PathVariable String id, - @RequestBody @Valid BookingCancelRequest request - ) { - bookingService.cancelBooking(id, request, 1L); //TODO: 인증된 memberId 지정 + @RequestBody @Valid BookingCancelRequest request) { + bookingService.cancelBooking(id, request, memberId); return ResponseEntity.ok().build(); } @@ -68,21 +69,21 @@ public ResponseEntity exitBooking(@PathVariable String id) { @GetMapping public ResponseEntity>> getBookings( - // @CurrentAccount Long memberId, + @CurrentAccount Long memberId, @ModelAttribute @Valid PageCondition pageCondition, @ModelAttribute @Valid BookingSearchCondition searchCondition ) { - PageResponse bookings = bookingService.getBookings(pageCondition, searchCondition, 1L); //TODO: 인증된 memberId 지정 + PageResponse bookings = bookingService.getBookings(pageCondition, searchCondition, memberId); ApiResponse> response = ApiResponse.ok(bookings); return ResponseEntity.ok().body(response); } @GetMapping("/{id}") public ResponseEntity> getBooking( - // @CurrentAccount Long memberId, + @CurrentAccount Long memberId, @PathVariable String id ) { - BookingGetResponse booking = bookingService.getBooking(id, 1L); //TODO: 인증된 memberId 지정 + BookingGetResponse booking = bookingService.getBooking(id, memberId); ApiResponse response = ApiResponse.ok(booking); return ResponseEntity.ok().body(response); } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java index 3593210c..79bd9728 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/booking/service/BookingService.java @@ -7,8 +7,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.scheduling.annotation.Async; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -45,7 +43,6 @@ import com.pgms.coredomain.domain.event.repository.EventTimeRepository; import com.pgms.coredomain.domain.member.Member; import com.pgms.coredomain.domain.member.repository.MemberRepository; -import com.pgms.coresecurity.security.jwt.booking.BookingAuthToken; import lombok.RequiredArgsConstructor; @@ -64,7 +61,7 @@ public class BookingService { //TODO: 테스트 코드 작성 private final TossPaymentConfig tossPaymentConfig; private final PaymentService paymentService; - public BookingCreateResponse createBooking(BookingCreateRequest request, Long memberId) { + public BookingCreateResponse createBooking(BookingCreateRequest request, Long memberId, String tokenSessionId) { Member member = getMemberById(memberId); EventTime time = getBookableTimeWithEvent(request.timeId()); List seats = getBookableSeatsWithArea(request.timeId(), request.seatIds()); @@ -91,7 +88,7 @@ public BookingCreateResponse createBooking(BookingCreateRequest request, Long me seats.forEach(seat -> seat.updateStatus(EventSeatStatus.BOOKED)); - removeSessionIdInBookingQueue(booking.getTime().getEvent().getId()); + removeSessionIdInBookingQueue(booking.getTime().getEvent().getId(), tokenSessionId); return BookingCreateResponse.of(booking, tossPaymentConfig.getSuccessUrl(), tossPaymentConfig.getFailUrl()); } @@ -164,8 +161,8 @@ public BookingGetResponse getBooking(String id, Long memberId) { } @Async - protected void removeSessionIdInBookingQueue(Long eventId) { - bookingQueueRepository.remove(eventId, getCurrentSessionId()); + protected void removeSessionIdInBookingQueue(Long eventId, String tokenSessionId) { + bookingQueueRepository.remove(eventId, tokenSessionId); } private EventTime getBookableTimeWithEvent(Long timeId) { @@ -212,12 +209,4 @@ private Member getMemberById(Long memberId) { return memberRepository.findById(memberId) .orElseThrow(() -> new BookingException(MemberErrorCode.MEMBER_NOT_FOUND)); } - - private String getCurrentSessionId() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication instanceof BookingAuthToken) { - return authentication.getPrincipal().toString(); - } - return null; - } } diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/repository/BookingQueueRepository.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/repository/BookingQueueRepository.java index b05a0385..c82a11db 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/repository/BookingQueueRepository.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/repository/BookingQueueRepository.java @@ -1,5 +1,7 @@ package com.pgms.apibooking.domain.bookingqueue.repository; +import java.util.Optional; + import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; @@ -15,8 +17,8 @@ public void add(Long eventId, String sessionId, double currentTimeSeconds) { redisTemplate.opsForZSet().add(String.valueOf(eventId), sessionId, currentTimeSeconds); } - public Long getRank(Long eventId, String sessionId) { - return redisTemplate.opsForZSet().rank(String.valueOf(eventId), sessionId); + public Optional getRank(Long eventId, String sessionId) { + return Optional.ofNullable(redisTemplate.opsForZSet().rank(String.valueOf(eventId), sessionId)); } public Long getEntryLimit() { diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/service/BookingQueueService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/service/BookingQueueService.java index ee37e9a9..83ef5491 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/service/BookingQueueService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/bookingqueue/service/BookingQueueService.java @@ -4,10 +4,9 @@ import org.springframework.stereotype.Service; -import com.pgms.coredomain.domain.common.BookingErrorCode; import com.pgms.apibooking.common.exception.BookingException; -import com.pgms.coresecurity.security.jwt.booking.BookingJwtPayload; -import com.pgms.coresecurity.security.jwt.booking.BookingJwtProvider; +import com.pgms.apibooking.common.jwt.BookingJwtPayload; +import com.pgms.apibooking.common.jwt.BookingJwtProvider; import com.pgms.apibooking.domain.bookingqueue.dto.request.BookingQueueEnterRequest; import com.pgms.apibooking.domain.bookingqueue.dto.request.BookingQueueExitRequest; import com.pgms.apibooking.domain.bookingqueue.dto.request.TokenIssueRequest; @@ -15,6 +14,7 @@ import com.pgms.apibooking.domain.bookingqueue.dto.response.SessionIdIssueResponse; import com.pgms.apibooking.domain.bookingqueue.dto.response.TokenIssueResponse; import com.pgms.apibooking.domain.bookingqueue.repository.BookingQueueRepository; +import com.pgms.coredomain.domain.common.BookingErrorCode; import lombok.RequiredArgsConstructor; @@ -31,7 +31,7 @@ public void enterQueue(BookingQueueEnterRequest request, String sessionId) { } public OrderInQueueGetResponse getOrderInQueue(Long eventId, String sessionId) { - Long myOrder = bookingQueueRepository.getRank(eventId, sessionId); + Long myOrder = getMyOrder(eventId, sessionId); Boolean isMyTurn = isMyTurn(eventId, sessionId); double currentTimeSeconds = System.currentTimeMillis() / 1000.0; @@ -53,7 +53,7 @@ public TokenIssueResponse issueToken(TokenIssueRequest request, String sessionId } private Boolean isMyTurn(Long eventId, String sessionId) { - Long myOrder = bookingQueueRepository.getRank(eventId, sessionId); + Long myOrder = getMyOrder(eventId, sessionId); Long entryLimit = bookingQueueRepository.getEntryLimit(); return myOrder <= entryLimit; } @@ -66,4 +66,9 @@ public SessionIdIssueResponse issueSessionId() { UUID sessionId = UUID.randomUUID(); return SessionIdIssueResponse.from(sessionId.toString()); } + + private Long getMyOrder(Long eventId, String sessionId) { + return bookingQueueRepository.getRank(eventId, sessionId) + .orElseThrow(() -> new BookingException(BookingErrorCode.NOT_IN_QUEUE)); + } } diff --git a/api/api-booking/src/main/resources/application.yml b/api/api-booking/src/main/resources/application.yml index a57d11b2..690e3380 100644 --- a/api/api-booking/src/main/resources/application.yml +++ b/api/api-booking/src/main/resources/application.yml @@ -17,3 +17,8 @@ payment: test-secret-api-key: test # 노출되면 안됨!! 임시값 넣어놓음 success-url: https://localhost:8080/api/v1/success fail-url: https://localhost:8080/api/v1/fail + +booking-jwt: + issuer: booking + secret-key: EENY5W0eegTf1naQB2eDeyCLl5kRS2b8xa5c4qLdS0hmVjtbvo8tOyhPMcAmtPu + expiry-seconds: 420 diff --git a/api/api-booking/src/test/java/com/pgms/apibooking/service/BookingServiceTest.java b/api/api-booking/src/test/java/com/pgms/apibooking/service/BookingServiceTest.java index c3261512..966a328c 100644 --- a/api/api-booking/src/test/java/com/pgms/apibooking/service/BookingServiceTest.java +++ b/api/api-booking/src/test/java/com/pgms/apibooking/service/BookingServiceTest.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -155,7 +156,7 @@ void setup() { ); // when - BookingCreateResponse response = bookingService.createBooking(request, member.getId()); + BookingCreateResponse response = bookingService.createBooking(request, member.getId(), UUID.randomUUID().toString()); // then Booking booking = bookingRepository.findBookingInfoById(response.bookingId()).get(); @@ -226,7 +227,7 @@ void setup() { ); // when & then - assertThatThrownBy(() -> bookingService.createBooking(request, member.getId())) + assertThatThrownBy(() -> bookingService.createBooking(request, member.getId(), UUID.randomUUID().toString())) .isInstanceOf(BookingException.class) .hasMessage(BookingErrorCode.UNBOOKABLE_EVENT.getMessage()); } @@ -278,7 +279,7 @@ void setup() { ); // when & then - assertThatThrownBy(() -> bookingService.createBooking(request, member.getId())) + assertThatThrownBy(() -> bookingService.createBooking(request, member.getId(), UUID.randomUUID().toString())) .isInstanceOf(BookingException.class) .hasMessage(BookingErrorCode.NON_EXISTENT_SEAT_INCLUSION.getMessage()); } @@ -328,7 +329,7 @@ void setup() { ); // when & then - assertThatThrownBy(() -> bookingService.createBooking(request, member.getId())) + assertThatThrownBy(() -> bookingService.createBooking(request, member.getId(), UUID.randomUUID().toString())) .isInstanceOf(BookingException.class) .hasMessage(BookingErrorCode.UNBOOKABLE_SEAT_INCLUSION.getMessage()); } @@ -378,7 +379,7 @@ void setup() { ); // when & then - assertThatThrownBy(() -> bookingService.createBooking(request, member.getId())) + assertThatThrownBy(() -> bookingService.createBooking(request, member.getId(), UUID.randomUUID().toString())) .isInstanceOf(BookingException.class) .hasMessage(BookingErrorCode.DELIVERY_ADDRESS_REQUIRED.getMessage()); } diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java index ca4168f2..1ef6ff55 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/common/BookingErrorCode.java @@ -40,6 +40,7 @@ public enum BookingErrorCode implements BaseErrorCode{ BOOKING_TOKEN_NOT_EXIST(HttpStatus.UNAUTHORIZED, "BOOKING_TOKEN_NOT_EXIST", "예매 토큰이 존재하지 않습니다."), INVALID_BOOKING_TOKEN(HttpStatus.UNAUTHORIZED, "INVALID_BOOKING_TOKEN", "올바르지 않은 예매 토큰입니다."), OUT_OF_ORDER(HttpStatus.BAD_REQUEST, "OUT_OF_ORDER", "예매 순서가 아닙니다."), + NOT_IN_QUEUE(HttpStatus.BAD_REQUEST, "NOT_IN_QUEUE", "대기열에 존재하지 않습니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "BOOKER_NOT_SAME", "권한이 없습니다."); diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingAuthToken.java b/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingAuthToken.java deleted file mode 100644 index d8486b72..00000000 --- a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingAuthToken.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.pgms.coresecurity.security.jwt.booking; - -import org.springframework.security.authentication.AbstractAuthenticationToken; - -public class BookingAuthToken extends AbstractAuthenticationToken { - - private final String sessionId; - - public BookingAuthToken(String sessionId) { - super(null); - this.sessionId = sessionId; - super.setAuthenticated(true); - } - - @Override - public Object getCredentials() { - return null; - } - - @Override - public Object getPrincipal() { - return this.sessionId; - } -} diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingExceptionHandlerFilter.java b/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingExceptionHandlerFilter.java deleted file mode 100644 index e0225ccb..00000000 --- a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingExceptionHandlerFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.pgms.coresecurity.security.jwt.booking; - -import static com.pgms.coredomain.domain.common.SecurityErrorCode.*; - -import java.io.IOException; - -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.pgms.coredomain.domain.common.BaseErrorCode; -import com.pgms.coredomain.domain.common.BookingErrorCode; -import com.pgms.coredomain.response.ErrorResponse; -import com.pgms.coresecurity.security.exception.SecurityCustomException; -import com.pgms.coresecurity.security.util.HttpResponseUtil; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; - -@Component -@Slf4j -public class BookingExceptionHandlerFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - try { - filterChain.doFilter(request, response); - } catch (SecurityCustomException e) { - BaseErrorCode bookingErrorCode = e.getErrorCode(); - sendFailResponse(response, bookingErrorCode); - } catch (Exception e) { - log.error(e.getMessage(), e); - BookingErrorCode bookingErrorCode = BookingErrorCode.INTERNAL_SERVER_ERROR; - sendFailResponse(response, bookingErrorCode); - } - } - - private void sendFailResponse(HttpServletResponse response, BaseErrorCode bookingErrorCode) throws IOException { - HttpResponseUtil.setErrorResponse(response, bookingErrorCode.getStatus(), bookingErrorCode.getErrorResponse()); - } -} diff --git a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtAuthFilter.java b/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtAuthFilter.java deleted file mode 100644 index 8212b00d..00000000 --- a/core/core-security/src/main/java/com/pgms/coresecurity/security/jwt/booking/BookingJwtAuthFilter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.pgms.coresecurity.security.jwt.booking; - -import java.io.IOException; - -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import com.pgms.coredomain.domain.common.BookingErrorCode; -import com.pgms.coresecurity.security.exception.SecurityCustomException; - -import io.jsonwebtoken.JwtException; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Component -@Slf4j -@RequiredArgsConstructor -public class BookingJwtAuthFilter extends OncePerRequestFilter { - - private final BookingJwtProvider bookingJwtProvider; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - String token = parseToken(request); - - if (token != null) { - try { - BookingJwtPayload authentication = bookingJwtProvider.validateAndParsePayload(token); - BookingAuthToken bookingAuthToken = new BookingAuthToken(authentication.sessionId()); - SecurityContextHolder.getContext().setAuthentication(bookingAuthToken); - } catch (JwtException | SecurityCustomException e) { - log.warn(e.getMessage(), e); - throw new SecurityCustomException(BookingErrorCode.INVALID_BOOKING_TOKEN); - } - } - - filterChain.doFilter(request, response); - } - - private String parseToken(HttpServletRequest request) { - String headerAuth = request.getHeader("Booking-Authorization"); - if (headerAuth != null && headerAuth.startsWith("Bearer ")) { - return headerAuth.substring(7); - } - return null; - } -} diff --git a/core/core-security/src/main/resources/application-security.yml b/core/core-security/src/main/resources/application-security.yml index 55110a63..8e922828 100644 --- a/core/core-security/src/main/resources/application-security.yml +++ b/core/core-security/src/main/resources/application-security.yml @@ -28,7 +28,3 @@ jwt: secret-key: EENY5W0eegTf1naQB2eDeyCLl5kRS2b8xa5c4qLdS0hmVjtbvo8tOyhPMcAmtPuQ # TODO : 시크릿 키 따로 관리 expiry-seconds: 1800 #30분 -booking-jwt: - issuer: booking - secret-key: EENY5W0eegTf1naQB2eDeyCLl5kRS2b8xa5c4qLdS0hmVjtbvo8tOyhPMcAmtPu - expiry-seconds: 420 From 2b56d0643badf8efc6548272c9be58f82bf69d0c Mon Sep 17 00:00:00 2001 From: Hanna Lee <8annahxxl@gmail.com> Date: Wed, 10 Jan 2024 13:50:25 +0900 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20seat=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20(#198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: seat 서비스 버그 수정 * fix: yml 롤백 --- api/api-booking/http/booking.http | 24 +++++++++++++++++-- .../domain/seat/service/SeatService.java | 12 +++++++--- .../resources/templates/booking_input.html | 4 ++-- .../coredomain/domain/booking/Booking.java | 2 +- core/core-infra/src/main/resources/data.sql | 12 +++++----- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/api/api-booking/http/booking.http b/api/api-booking/http/booking.http index 31676658..b33c8c47 100644 --- a/api/api-booking/http/booking.http +++ b/api/api-booking/http/booking.http @@ -43,8 +43,28 @@ 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 @@ -52,7 +72,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwic3ViIjoiam9obi5kb2VAZXhhb 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 diff --git a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java index a91a3dd5..a3575bb1 100644 --- a/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java +++ b/api/api-booking/src/main/java/com/pgms/apibooking/domain/seat/service/SeatService.java @@ -40,6 +40,12 @@ public List getSeats(SeatsGetRequest request) { public void selectSeat(Long seatId, Long memberId) { if (seatLockService.isSeatLocked(seatId)) { + Optional selectorIdOpt = seatLockService.getSelectorId(seatId); + + if (selectorIdOpt.isPresent() && selectorIdOpt.get().equals(memberId)) { + return; + } + throw new BookingException(BookingErrorCode.SEAT_BEING_BOOKED); } @@ -56,11 +62,11 @@ public void selectSeat(Long seatId, Long memberId) { public void deselectSeat(Long seatId, Long memberId) { Optional 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); } diff --git a/api/api-booking/src/main/resources/templates/booking_input.html b/api/api-booking/src/main/resources/templates/booking_input.html index d00b5cfb..f449fbad 100644 --- a/api/api-booking/src/main/resources/templates/booking_input.html +++ b/api/api-booking/src/main/resources/templates/booking_input.html @@ -121,10 +121,10 @@

주문 정보 입력

$.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), diff --git a/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java b/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java index 4fd971c3..962e120d 100644 --- a/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java +++ b/core/core-domain/src/main/java/com/pgms/coredomain/domain/booking/Booking.java @@ -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() { diff --git a/core/core-infra/src/main/resources/data.sql b/core/core-infra/src/main/resources/data.sql index 734d6aa0..a0a6be73 100644 --- a/core/core-infra/src/main/resources/data.sql +++ b/core/core-infra/src/main/resources/data.sql @@ -6,13 +6,13 @@ VALUES ('고척스카이돔', '서울 구로구 경인로 430'); INSERT INTO event (title, description, running_time, started_at, ended_at, rating, genre, average_score, thumbnail, booking_started_at, booking_ended_at, event_hall_id) VALUES ('BLACKPINK WORLD TOUR [BORN PINK] FINALE IN SEOUL', 'BLACKPINK WORLD TOUR [BORN PINK] FINALE IN SEOUL', 120, - '2024-01-05T10:00:00', '2025-01-06T12:00:00', '15세 이상 관람가', 'CONCERT', 0.0, + '2025-01-01T10:00:00', '2025-01-01T12:00:00', '15세 이상 관람가', 'CONCERT', 0.0, 'https://ticketimage.interpark.com/Play/image/large/23/23011804_p.gif', '2023-12-21T09:00:00', '2024-12-31T11:00:00', 1); -- EventTime INSERT INTO event_time (round, started_at, ended_at, event_id) -VALUES (1, '2024-01-01T10:00:00', '2024-01-01T12:00:00', 1); +VALUES (1, '2025-01-01T10:00:00', '2025-01-01T12:00:00', 1); -- EventSeatArea INSERT INTO event_seat_area (price, area_type, event_id) @@ -23,10 +23,10 @@ VALUES (100000, 'S', 1), INSERT INTO event_seat (name, status, event_seat_area_id, event_time_id) VALUES ('A1', 'BEING_BOOKED', 1, 1) , ('A2', 'BEING_BOOKED', 1, 1) - , ('A3', 'BEING_BOOKED', 1, 1) - , ('E1', 'BEING_BOOKED', 2, 1) - , ('E2', 'BEING_BOOKED', 2, 1) - , ('E3', 'BEING_BOOKED', 2, 1); + , ('A3', 'AVAILABLE', 1, 1) + , ('E1', 'AVAILABLE', 2, 1) + , ('E2', 'AVAILABLE', 2, 1) + , ('E3', 'AVAILABLE', 2, 1); -- EventReview INSERT INTO event_review (score, content, event_id)