From 1dc7f2b0593ff334f8e836a42c00c41606043f4b Mon Sep 17 00:00:00 2001 From: Eunjin Kim Date: Thu, 27 Jun 2024 00:25:52 +0900 Subject: [PATCH] =?UTF-8?q?:sparkles:=20Feat:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apiPayload/code/status/ErrorStatus.java | 7 ++- .../exception/handler/ReviewHandler.java | 10 ++++ .../converter/review/ReviewConverter.java | 38 +++++++++++++ .../com/umc/TheGoods/domain/item/Review.java | 22 +++++++- .../umc/TheGoods/domain/order/OrderItem.java | 5 +- .../repository/review/ReviewRepository.java | 10 ++++ .../ReviewService/ReviewCommandService.java | 10 ++++ .../ReviewCommandServiceImpl.java | 55 +++++++++++++++++++ .../web/controller/ReviewController.java | 52 ++++++++++++++++++ .../web/dto/review/ReviewRequestDTO.java | 25 +++++++++ .../web/dto/review/ReviewResponseDTO.java | 26 +++++++++ 11 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/umc/TheGoods/apiPayload/exception/handler/ReviewHandler.java create mode 100644 src/main/java/com/umc/TheGoods/converter/review/ReviewConverter.java create mode 100644 src/main/java/com/umc/TheGoods/repository/review/ReviewRepository.java create mode 100644 src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandService.java create mode 100644 src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandServiceImpl.java create mode 100644 src/main/java/com/umc/TheGoods/web/controller/ReviewController.java create mode 100644 src/main/java/com/umc/TheGoods/web/dto/review/ReviewRequestDTO.java create mode 100644 src/main/java/com/umc/TheGoods/web/dto/review/ReviewResponseDTO.java diff --git a/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java index 99f8a72e..473a4494 100644 --- a/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/umc/TheGoods/apiPayload/code/status/ErrorStatus.java @@ -44,6 +44,9 @@ public enum ErrorStatus implements BaseErrorCode { CART_DETAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "CART4004", "해당 장바구니 상세 내역을 찾을 수 없습니다"), DELETE_CART_DETAIL_FAILED(HttpStatus.BAD_REQUEST, "CART4005", "장바구니 상세 내역을 삭제할 수 없습니다."), + // 리뷰 관련 에러 + REVIEW_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "REVIEW4001", "이미 리뷰를 작성한 주문내역 입니다."), + // test TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "테스트"), @@ -59,7 +62,7 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER4009", "주소가 존재하지 않습니다"), MEMBER_INACTIVATE(HttpStatus.NOT_ACCEPTABLE, "MEMBER4010", "탈퇴한 회원입니다."), MEMBER_NOT_OWNER(HttpStatus.NOT_ACCEPTABLE, "MEMBER4011", "해당 회원이 아닙니다."), - MEMBER_CONTACT_NOT_FOUND(HttpStatus.NOT_FOUND,"MEMBER4012", "연락 가능 시간을 조회할 수 없습니다."), + MEMBER_CONTACT_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER4012", "연락 가능 시간을 조회할 수 없습니다."), MEMBER_EMAIL_DUPLICATED(HttpStatus.BAD_REQUEST, "MEMBER4013", "중복된 이메일입니다."), MEMBER_EMAIL_INCORRECT(HttpStatus.BAD_REQUEST, "MEMBER4014", "잘못된 이메일입니다."), @@ -77,7 +80,7 @@ public enum ErrorStatus implements BaseErrorCode { POST_ALREADY_FOLLOW(HttpStatus.BAD_REQUEST, "POST4002", "이미 팔로우 했습니다."), POST_FOLLOW_NOT_FOUND(HttpStatus.BAD_REQUEST, "POST4003", "해당 팔로우를 찾을수 없습니다."), POST_NOT_FOUND(HttpStatus.BAD_REQUEST, "POST4004", "포스트를 찾을수 없습니다."), - POST_COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST,"POST4005", "해당 댓글을 찾을 수 없습니다."), + POST_COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "POST4005", "해당 댓글을 찾을 수 없습니다."), POST_COMMENT_NOT_UPDATE(HttpStatus.BAD_REQUEST, "POST4006", "해당 댓글을 수정할 수 없습니다."), diff --git a/src/main/java/com/umc/TheGoods/apiPayload/exception/handler/ReviewHandler.java b/src/main/java/com/umc/TheGoods/apiPayload/exception/handler/ReviewHandler.java new file mode 100644 index 00000000..06107005 --- /dev/null +++ b/src/main/java/com/umc/TheGoods/apiPayload/exception/handler/ReviewHandler.java @@ -0,0 +1,10 @@ +package com.umc.TheGoods.apiPayload.exception.handler; + +import com.umc.TheGoods.apiPayload.code.BaseErrorCode; +import com.umc.TheGoods.apiPayload.exception.GeneralException; + +public class ReviewHandler extends GeneralException { + public ReviewHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/com/umc/TheGoods/converter/review/ReviewConverter.java b/src/main/java/com/umc/TheGoods/converter/review/ReviewConverter.java new file mode 100644 index 00000000..c1c9c9bc --- /dev/null +++ b/src/main/java/com/umc/TheGoods/converter/review/ReviewConverter.java @@ -0,0 +1,38 @@ +package com.umc.TheGoods.converter.review; + +import com.umc.TheGoods.domain.enums.ReviewStatus; +import com.umc.TheGoods.domain.item.Review; +import com.umc.TheGoods.domain.order.OrderItem; +import com.umc.TheGoods.web.dto.review.ReviewResponseDTO; + +import java.util.List; +import java.util.stream.Collectors; + +public class ReviewConverter { + + public static Review toReview(OrderItem orderItem, String text, Integer score) { + return Review.builder() + .text(text) + .score(score) + .orderItem(orderItem) + .status(ReviewStatus.SHOW) + .build(); + } + + public static ReviewResponseDTO.reviewPostDTO toReviewPostDTO(Review review) { + return ReviewResponseDTO.reviewPostDTO.builder() + .reviewId(review.getId()) + .createdAt(review.getCreatedAt()) + .itemName(review.getItem().getName()) + .score(review.getScore()) + .text(review.getText()) + .optionStringList(ReviewConverter.toOptionStringList(review.getOrderItem())) + .build(); + } + + private static List toOptionStringList(OrderItem orderItem) { + return orderItem.getOrderDetailList().stream().map(orderDetail -> { + return orderDetail.getItemOption().getName(); + }).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/umc/TheGoods/domain/item/Review.java b/src/main/java/com/umc/TheGoods/domain/item/Review.java index f0eff75d..f0ef1f1a 100644 --- a/src/main/java/com/umc/TheGoods/domain/item/Review.java +++ b/src/main/java/com/umc/TheGoods/domain/item/Review.java @@ -2,7 +2,6 @@ import com.umc.TheGoods.domain.common.BaseDateTimeEntity; import com.umc.TheGoods.domain.enums.ReviewStatus; -import com.umc.TheGoods.domain.images.ReviewImg; import com.umc.TheGoods.domain.member.Member; import com.umc.TheGoods.domain.order.OrderItem; import lombok.*; @@ -44,8 +43,25 @@ public class Review extends BaseDateTimeEntity { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_item_id", nullable = false) private OrderItem orderItem; +// +// @OneToOne(mappedBy = "review", cascade = CascadeType.ALL) +// private ReviewImg reviewImg; - @OneToOne(mappedBy = "review", cascade = CascadeType.ALL) - private ReviewImg reviewImg; + // 연관관계 메소드 + public void setMember(Member member) { + if (this.member != null) { + this.member.getReviewList().remove(this); + } + this.member = member; + this.member.getReviewList().add(this); + } + + public void setItem(Item item) { + if (this.item != null) { + this.item.getReviewList().remove(this); + } + this.item = item; + this.item.getReviewList().add(this); + } } diff --git a/src/main/java/com/umc/TheGoods/domain/order/OrderItem.java b/src/main/java/com/umc/TheGoods/domain/order/OrderItem.java index d1a3ad98..af5b2f11 100644 --- a/src/main/java/com/umc/TheGoods/domain/order/OrderItem.java +++ b/src/main/java/com/umc/TheGoods/domain/order/OrderItem.java @@ -3,7 +3,6 @@ import com.umc.TheGoods.domain.common.BaseDateTimeEntity; import com.umc.TheGoods.domain.enums.OrderStatus; import com.umc.TheGoods.domain.item.Item; -import com.umc.TheGoods.domain.item.Review; import com.umc.TheGoods.domain.types.DeliveryType; import com.umc.TheGoods.web.dto.order.OrderRequestDTO; import lombok.*; @@ -87,8 +86,8 @@ public class OrderItem extends BaseDateTimeEntity { @OneToOne(mappedBy = "orderItem", cascade = CascadeType.ALL) private OrderCancel orderCancel; - @OneToOne(mappedBy = "orderItem", cascade = CascadeType.ALL) - private Review review; +// @OneToOne(mappedBy = "orderItem", cascade = CascadeType.ALL) +// private Review review; public void setOrders(Orders orders) { if (this.orders != null) { diff --git a/src/main/java/com/umc/TheGoods/repository/review/ReviewRepository.java b/src/main/java/com/umc/TheGoods/repository/review/ReviewRepository.java new file mode 100644 index 00000000..94c58148 --- /dev/null +++ b/src/main/java/com/umc/TheGoods/repository/review/ReviewRepository.java @@ -0,0 +1,10 @@ +package com.umc.TheGoods.repository.review; + +import com.umc.TheGoods.domain.item.Review; +import com.umc.TheGoods.domain.order.OrderItem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReviewRepository extends JpaRepository { + + boolean existsByOrderItem(OrderItem orderItem); +} diff --git a/src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandService.java b/src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandService.java new file mode 100644 index 00000000..f075c04d --- /dev/null +++ b/src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandService.java @@ -0,0 +1,10 @@ +package com.umc.TheGoods.service.ReviewService; + +import com.umc.TheGoods.domain.item.Review; +import com.umc.TheGoods.domain.member.Member; +import com.umc.TheGoods.web.dto.review.ReviewRequestDTO; + +public interface ReviewCommandService { + + Review create(ReviewRequestDTO.addReviewDTO request, Member member); +} diff --git a/src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandServiceImpl.java b/src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandServiceImpl.java new file mode 100644 index 00000000..88c81097 --- /dev/null +++ b/src/main/java/com/umc/TheGoods/service/ReviewService/ReviewCommandServiceImpl.java @@ -0,0 +1,55 @@ +package com.umc.TheGoods.service.ReviewService; + +import com.umc.TheGoods.apiPayload.code.status.ErrorStatus; +import com.umc.TheGoods.apiPayload.exception.handler.OrderHandler; +import com.umc.TheGoods.apiPayload.exception.handler.ReviewHandler; +import com.umc.TheGoods.converter.review.ReviewConverter; +import com.umc.TheGoods.domain.item.Review; +import com.umc.TheGoods.domain.member.Member; +import com.umc.TheGoods.domain.order.OrderItem; +import com.umc.TheGoods.repository.order.OrderItemRepository; +import com.umc.TheGoods.repository.review.ReviewRepository; +import com.umc.TheGoods.web.dto.review.ReviewRequestDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional +public class ReviewCommandServiceImpl implements ReviewCommandService { + + private final OrderItemRepository orderItemRepository; + private final ReviewRepository reviewRepository; + + /** + * 리뷰 등록 + * + * @param request + * @param member + * @return + */ + @Override + public Review create(ReviewRequestDTO.addReviewDTO request, Member member) { + // 해당 orderItem이 해당 회원의 것이 맞는지 검증 + OrderItem orderItem = orderItemRepository.findById(request.getOrderItemId()).orElseThrow(() -> new OrderHandler(ErrorStatus.ORDER_ITEM_NOT_FOUND)); + if (!orderItem.getOrders().getMember().equals(member)) { + throw new ReviewHandler(ErrorStatus.NOT_ORDER_OWNER); + } + + // 이미 리뷰를 등록했던 orderItem인지 검증 + boolean isExists = reviewRepository.existsByOrderItem(orderItem); + if (isExists) { + throw new ReviewHandler(ErrorStatus.REVIEW_ALREADY_EXISTS); + } + + // review 엔티티 생성 및 연관관계 매핑 + Review review = ReviewConverter.toReview(orderItem, request.getText(), request.getScore()); + review.setMember(member); + review.setItem(orderItem.getItem()); + + return reviewRepository.save(review); + } +} diff --git a/src/main/java/com/umc/TheGoods/web/controller/ReviewController.java b/src/main/java/com/umc/TheGoods/web/controller/ReviewController.java new file mode 100644 index 00000000..4a8a61f7 --- /dev/null +++ b/src/main/java/com/umc/TheGoods/web/controller/ReviewController.java @@ -0,0 +1,52 @@ +package com.umc.TheGoods.web.controller; + +import com.umc.TheGoods.apiPayload.ApiResponse; +import com.umc.TheGoods.apiPayload.code.status.ErrorStatus; +import com.umc.TheGoods.apiPayload.exception.handler.MemberHandler; +import com.umc.TheGoods.converter.review.ReviewConverter; +import com.umc.TheGoods.domain.item.Review; +import com.umc.TheGoods.domain.member.Member; +import com.umc.TheGoods.service.MemberService.MemberQueryService; +import com.umc.TheGoods.service.ReviewService.ReviewCommandService; +import com.umc.TheGoods.web.dto.review.ReviewRequestDTO; +import com.umc.TheGoods.web.dto.review.ReviewResponseDTO; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.validation.annotation.Validated; +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 javax.validation.Valid; + +@Slf4j +@Tag(name = "Review", description = "리뷰 관련 API") +@Validated +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/review") +public class ReviewController { + private final MemberQueryService memberQueryService; + private final ReviewCommandService reviewCommandService; + + @PostMapping + public ApiResponse addReview(@RequestBody @Valid ReviewRequestDTO.addReviewDTO request, + Authentication authentication) { + // 비회원인 경우 처리 불가 + if (authentication == null) { + throw new MemberHandler(ErrorStatus._UNAUTHORIZED); + } + + // request에서 member id 추출해 Member 엔티티 찾기 + Member member = memberQueryService.findMemberById(Long.valueOf(authentication.getName().toString())).orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + + // 리뷰 등록 + Review review = reviewCommandService.create(request, member); + + return ApiResponse.onSuccess(ReviewConverter.toReviewPostDTO(review)); + } + +} diff --git a/src/main/java/com/umc/TheGoods/web/dto/review/ReviewRequestDTO.java b/src/main/java/com/umc/TheGoods/web/dto/review/ReviewRequestDTO.java new file mode 100644 index 00000000..c4a7529a --- /dev/null +++ b/src/main/java/com/umc/TheGoods/web/dto/review/ReviewRequestDTO.java @@ -0,0 +1,25 @@ +package com.umc.TheGoods.web.dto.review; + +import lombok.Getter; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class ReviewRequestDTO { + + @Getter + public static class addReviewDTO { + @NotNull + @Min(1) + @Max(5) + Integer score; + + @NotNull + Long orderItemId; + + @Size(max = 1500) + String text; + } +} diff --git a/src/main/java/com/umc/TheGoods/web/dto/review/ReviewResponseDTO.java b/src/main/java/com/umc/TheGoods/web/dto/review/ReviewResponseDTO.java new file mode 100644 index 00000000..00fec441 --- /dev/null +++ b/src/main/java/com/umc/TheGoods/web/dto/review/ReviewResponseDTO.java @@ -0,0 +1,26 @@ +package com.umc.TheGoods.web.dto.review; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class ReviewResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class reviewPostDTO { + Long reviewId; + LocalDateTime createdAt; + String itemName; + @JsonInclude(JsonInclude.Include.NON_NULL) + List optionStringList; + Integer score; + String text; + } +}