From 4e192917a0792f6eec7fb41785cf86b1325fba20 Mon Sep 17 00:00:00 2001 From: gomin0 Date: Thu, 1 Aug 2024 16:26:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:sparkles:=20feat:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 21 +++++++----- .../review/dto/ReviewReportRequest.java | 31 +++++++++++++++++ .../review/dto/ReviewReportResponse.java | 34 +++++++++++++++++++ .../domain/review/entity/ReportType.java | 8 +++++ .../domain/review/entity/ReviewReport.java | 34 +++++++++++++++++++ .../repository/ReviewReportRepository.java | 7 ++++ .../review/service/ReviewCommandService.java | 26 ++++++++++++++ 7 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 src/main/java/umc/unimade/domain/review/dto/ReviewReportRequest.java create mode 100644 src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java create mode 100644 src/main/java/umc/unimade/domain/review/entity/ReportType.java create mode 100644 src/main/java/umc/unimade/domain/review/entity/ReviewReport.java create mode 100644 src/main/java/umc/unimade/domain/review/repository/ReviewReportRepository.java diff --git a/src/main/java/umc/unimade/domain/review/controller/ReviewController.java b/src/main/java/umc/unimade/domain/review/controller/ReviewController.java index c627e26..1616f78 100644 --- a/src/main/java/umc/unimade/domain/review/controller/ReviewController.java +++ b/src/main/java/umc/unimade/domain/review/controller/ReviewController.java @@ -7,11 +7,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import umc.unimade.domain.accounts.entity.Buyer; import umc.unimade.domain.accounts.repository.BuyerRepository; +import umc.unimade.domain.review.dto.ReviewReportRequest; +import umc.unimade.domain.review.dto.ReviewReportResponse; import umc.unimade.domain.review.dto.ReviewCreateRequest; import umc.unimade.domain.review.dto.ReviewResponse; import umc.unimade.domain.review.service.ReviewCommandService; @@ -19,12 +20,12 @@ import umc.unimade.global.common.ApiResponse; import umc.unimade.global.common.ErrorCode; import umc.unimade.domain.accounts.exception.UserExceptionHandler; -import umc.unimade.global.security.UserId; import java.util.List; @RestController @RequestMapping("/api/review") +@Tag(name = "Review", description = "리뷰 관련 API") @RequiredArgsConstructor public class ReviewController { private final ReviewCommandService reviewCommandService; @@ -32,7 +33,6 @@ public class ReviewController { //임시로 추가 private final BuyerRepository buyerRepository; - @Tag(name = "Review", description = "리뷰 관련 API") @Operation(summary = "리뷰 생성, buyerId는 나중에 뺄게요!") @PostMapping(value = "/{productId}/{buyerId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> createReview(@PathVariable Long productId, @@ -50,7 +50,6 @@ public ResponseEntity> createReview(@PathVariable Long product } } - @Tag(name = "Review", description = "리뷰 관련 API") @Operation(summary = "리뷰 상세 내용 불러오기 ") @GetMapping("/{reviewId}") //To do : user 토큰 추가 @@ -63,8 +62,8 @@ public ResponseEntity> getReview(@PathVariable Long return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.onFailure(ErrorCode.BUYER_NOT_FOUND.getCode(), ErrorCode.BUYER_NOT_FOUND.getMessage())); } } - @Tag(name = "Review", description = "리뷰 관련 API") - @Operation(summary = "리뷰 삭제하기, description = buyerId 나중에 뺄게요! ") + + @Operation(summary = "리뷰 삭제하기", description = "buyerId 나중에 뺄게요! ") @DeleteMapping("/{reviewId}/{buyerId}") public ResponseEntity> deleteReview(@PathVariable Long reviewId, @PathVariable Long buyerId) { @@ -79,13 +78,19 @@ public ResponseEntity> deleteReview(@PathVariable Long reviewI } } - - // 임시로 추가 private Buyer findBuyerById(Long buyerId) { return buyerRepository.findById(buyerId) .orElseThrow(() -> new UserExceptionHandler(ErrorCode.BUYER_NOT_FOUND)); } + + // TODO: seller 받아 오기 + @Operation(summary = "리뷰 신고하기", description = "seller가 자신의 상품에 달린 리뷰를 신고") + @PostMapping("/{reviewId}/report") + public ApiResponse reportReview(@PathVariable Long reviewId, @RequestBody ReviewReportRequest request) { + ReviewReportResponse response = reviewCommandService.reportReview(reviewId, request); + return ApiResponse.onSuccess(response); + } } diff --git a/src/main/java/umc/unimade/domain/review/dto/ReviewReportRequest.java b/src/main/java/umc/unimade/domain/review/dto/ReviewReportRequest.java new file mode 100644 index 0000000..a55fea7 --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/dto/ReviewReportRequest.java @@ -0,0 +1,31 @@ +package umc.unimade.domain.review.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import umc.unimade.domain.accounts.entity.Seller; +import umc.unimade.domain.review.entity.ReportType; +import umc.unimade.domain.review.entity.Review; +import umc.unimade.domain.review.entity.ReviewReport; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ReviewReportRequest { + + @NotNull + private ReportType type; + private String description; + + public ReviewReport toEntity(Review review, Seller seller) { + return ReviewReport.builder() + .review(review) + .seller(seller) + .type(type) + .description(description) + .build(); + } +} diff --git a/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java b/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java new file mode 100644 index 0000000..c7a60bf --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java @@ -0,0 +1,34 @@ +package umc.unimade.domain.review.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import umc.unimade.domain.review.entity.ReportType; +import umc.unimade.domain.review.entity.ReviewReport; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ReviewReportResponse { + + private Long reportId; + private Long reviewId; + private Long buyerId; + private Long sellerId; + private ReportType type; + private String description; + + public static ReviewReportResponse from(ReviewReport report) { + return ReviewReportResponse.builder() + .reportId(report.getId()) + .reviewId(report.getReview().getId()) + .buyerId(report.getReview().getBuyer().getId()) + .sellerId(report.getSeller().getId()) + .type(report.getType()) + .description(report.getDescription()) + .build(); + } + +} diff --git a/src/main/java/umc/unimade/domain/review/entity/ReportType.java b/src/main/java/umc/unimade/domain/review/entity/ReportType.java new file mode 100644 index 0000000..780f1c5 --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/entity/ReportType.java @@ -0,0 +1,8 @@ +package umc.unimade.domain.review.entity; + +public enum ReportType { + OPTION_1, + OPTION_2, + OPTION_3, + ECT +} diff --git a/src/main/java/umc/unimade/domain/review/entity/ReviewReport.java b/src/main/java/umc/unimade/domain/review/entity/ReviewReport.java new file mode 100644 index 0000000..bd1f3f6 --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/entity/ReviewReport.java @@ -0,0 +1,34 @@ +package umc.unimade.domain.review.entity; + +import jakarta.persistence.*; +import lombok.*; +import umc.unimade.domain.accounts.entity.Seller; +import umc.unimade.global.common.BaseEntity; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Table(name = "review_report") +public class ReviewReport extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "review_report_id", nullable = false) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "review_id", nullable = false) + private Review review; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "seller_id", nullable = false) + private Seller seller; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private ReportType type; + + @Column(name = "description") + private String description; +} \ No newline at end of file diff --git a/src/main/java/umc/unimade/domain/review/repository/ReviewReportRepository.java b/src/main/java/umc/unimade/domain/review/repository/ReviewReportRepository.java new file mode 100644 index 0000000..b52c1ef --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/repository/ReviewReportRepository.java @@ -0,0 +1,7 @@ +package umc.unimade.domain.review.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.unimade.domain.review.entity.ReviewReport; + +public interface ReviewReportRepository extends JpaRepository { +} diff --git a/src/main/java/umc/unimade/domain/review/service/ReviewCommandService.java b/src/main/java/umc/unimade/domain/review/service/ReviewCommandService.java index 20ac35d..bc5267f 100644 --- a/src/main/java/umc/unimade/domain/review/service/ReviewCommandService.java +++ b/src/main/java/umc/unimade/domain/review/service/ReviewCommandService.java @@ -5,9 +5,16 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import umc.unimade.domain.accounts.entity.Buyer; +import umc.unimade.domain.accounts.entity.Seller; +import umc.unimade.domain.accounts.exception.SellerExceptionHandler; +import umc.unimade.domain.accounts.repository.SellerRepository; import umc.unimade.domain.products.repository.ProductRepository; import umc.unimade.domain.products.entity.Products; +import umc.unimade.domain.review.dto.ReviewReportRequest; +import umc.unimade.domain.review.dto.ReviewReportResponse; +import umc.unimade.domain.review.entity.ReviewReport; import umc.unimade.domain.review.exception.ReviewExceptionHandler; +import umc.unimade.domain.review.repository.ReviewReportRepository; import umc.unimade.domain.review.repository.ReviewRepository; import umc.unimade.domain.review.dto.ReviewCreateRequest; import umc.unimade.domain.review.entity.Review; @@ -24,6 +31,8 @@ public class ReviewCommandService { private final ReviewRepository reviewRepository; private final ProductRepository productRepository; private final S3Provider s3Provider; + private final SellerRepository sellerRepository; + private final ReviewReportRepository reviewReportRepository; @Transactional public void createReview(Long productId, Buyer buyer, ReviewCreateRequest reviewCreateRequest, List images) { @@ -57,4 +66,21 @@ private Review findReviewById(Long reviewId){ return reviewRepository.findById(reviewId) .orElseThrow(() -> new ReviewExceptionHandler(ErrorCode.REVIEW_NOT_FOUND)); } + + @Transactional + public ReviewReportResponse reportReview(Long reviewId, ReviewReportRequest request) { + + Review review = reviewRepository.findById(reviewId) + .orElseThrow(() -> new ReviewExceptionHandler(ErrorCode.REVIEW_NOT_FOUND)); + + // TODO: 컨트롤러에서 seller 받아오기 + Seller seller = sellerRepository.findById(1L) + .orElseThrow(() -> new SellerExceptionHandler(ErrorCode.SELLER_NOT_FOUND)); + + + ReviewReport report = request.toEntity(review, seller); + reviewReportRepository.save(report); + + return ReviewReportResponse.from(report); + } } From c42ff95f7fcbc28e8e6b8072d7876ab3c314f939 Mon Sep 17 00:00:00 2001 From: gomin0 Date: Thu, 1 Aug 2024 17:37:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:sparkles:=20feat:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EC=A1=B0=ED=9A=8C=20=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminReviewController.java | 43 +++++++++++++++++++ .../review/dto/ReviewReportResponse.java | 10 ++++- .../review/dto/ReviewReportsResponse.java | 28 ++++++++++++ .../service/ReviewReportQueryService.java | 32 ++++++++++++++ .../umc/unimade/global/common/ErrorCode.java | 1 + 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/main/java/umc/unimade/domain/review/controller/AdminReviewController.java create mode 100644 src/main/java/umc/unimade/domain/review/dto/ReviewReportsResponse.java create mode 100644 src/main/java/umc/unimade/domain/review/service/ReviewReportQueryService.java diff --git a/src/main/java/umc/unimade/domain/review/controller/AdminReviewController.java b/src/main/java/umc/unimade/domain/review/controller/AdminReviewController.java new file mode 100644 index 0000000..aa9d7b8 --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/controller/AdminReviewController.java @@ -0,0 +1,43 @@ +package umc.unimade.domain.review.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.web.bind.annotation.*; +import org.springframework.data.domain.Pageable; +import umc.unimade.domain.review.dto.ReviewReportResponse; +import umc.unimade.domain.review.dto.ReviewReportsResponse; +import umc.unimade.domain.review.service.ReviewReportQueryService; +import umc.unimade.global.common.ApiResponse; + +@RestController +@RequestMapping("/admin/report") +@Tag(name = "Admin_report", description = "관리자-리뷰 신고 관련 API") +@RequiredArgsConstructor +public class AdminReviewController { + + private final ReviewReportQueryService reviewReportQueryService; + + // TODO: 신고 처리 여부에 따라 조회 + @Operation(summary = "리뷰 신고 목록 조회", description = "유저 role이 관리자인 사람만 가능") + @GetMapping() + public ApiResponse> getReports( + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "10") int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + Page responses = reviewReportQueryService.getReports(pageable); + return ApiResponse.onSuccess(responses); + } + + @Operation(summary = "리뷰 신고 개별 조회", description = "유저 role이 관리자인 사람만 가능") + @GetMapping("/{reviewReportId}") + public ApiResponse getReport(@PathVariable Long reviewReportId) { + ReviewReportResponse response = reviewReportQueryService.getReport(reviewReportId); + return ApiResponse.onSuccess(response); + } + + // TODO: 관리자의 신고 처리 추가 +} diff --git a/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java b/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java index c7a60bf..d5d718e 100644 --- a/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java +++ b/src/main/java/umc/unimade/domain/review/dto/ReviewReportResponse.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import umc.unimade.domain.review.entity.ReportType; +import umc.unimade.domain.review.entity.Review; import umc.unimade.domain.review.entity.ReviewReport; @Getter @@ -18,15 +19,20 @@ public class ReviewReportResponse { private Long buyerId; private Long sellerId; private ReportType type; + private String reviewTitle; + private String reviewContent; private String description; public static ReviewReportResponse from(ReviewReport report) { + Review review = report.getReview(); return ReviewReportResponse.builder() .reportId(report.getId()) - .reviewId(report.getReview().getId()) - .buyerId(report.getReview().getBuyer().getId()) + .reviewId(review.getId()) + .buyerId(review.getBuyer().getId()) .sellerId(report.getSeller().getId()) .type(report.getType()) + .reviewTitle(review.getTitle()) + .reviewContent(review.getContent()) .description(report.getDescription()) .build(); } diff --git a/src/main/java/umc/unimade/domain/review/dto/ReviewReportsResponse.java b/src/main/java/umc/unimade/domain/review/dto/ReviewReportsResponse.java new file mode 100644 index 0000000..b63f492 --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/dto/ReviewReportsResponse.java @@ -0,0 +1,28 @@ +package umc.unimade.domain.review.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import umc.unimade.domain.review.entity.Review; +import umc.unimade.domain.review.entity.ReviewReport; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ReviewReportsResponse { + + private Long reportId; + private Long reviewId; + private String reviewTitle; + + public static ReviewReportsResponse from(ReviewReport report) { + Review review = report.getReview(); + return ReviewReportsResponse.builder() + .reportId(report.getId()) + .reviewId(review.getId()) + .reviewTitle(review.getTitle()) + .build(); + } +} diff --git a/src/main/java/umc/unimade/domain/review/service/ReviewReportQueryService.java b/src/main/java/umc/unimade/domain/review/service/ReviewReportQueryService.java new file mode 100644 index 0000000..a692fc8 --- /dev/null +++ b/src/main/java/umc/unimade/domain/review/service/ReviewReportQueryService.java @@ -0,0 +1,32 @@ +package umc.unimade.domain.review.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.unimade.domain.review.dto.ReviewReportResponse; +import umc.unimade.domain.review.dto.ReviewReportsResponse; +import umc.unimade.domain.review.entity.ReviewReport; +import umc.unimade.domain.review.exception.ReviewExceptionHandler; +import umc.unimade.domain.review.repository.ReviewReportRepository; +import umc.unimade.global.common.ErrorCode; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ReviewReportQueryService { + + private final ReviewReportRepository reviewReportRepository; + + public Page getReports(Pageable pageable) { + Page reports = reviewReportRepository.findAll(pageable); + return reports.map(ReviewReportsResponse::from); + } + + public ReviewReportResponse getReport(Long reviewReportId) { + ReviewReport report = reviewReportRepository.findById(reviewReportId) + .orElseThrow(() -> new ReviewExceptionHandler(ErrorCode.REPORT_NOT_FOUND)); + return ReviewReportResponse.from(report); + } +} diff --git a/src/main/java/umc/unimade/global/common/ErrorCode.java b/src/main/java/umc/unimade/global/common/ErrorCode.java index f897482..ddbb888 100644 --- a/src/main/java/umc/unimade/global/common/ErrorCode.java +++ b/src/main/java/umc/unimade/global/common/ErrorCode.java @@ -50,6 +50,7 @@ public enum ErrorCode implements BaseErrorCode { REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW4000", "리뷰를 찾을 수 없습니다."), INVALID_RATING_STAR(HttpStatus.BAD_REQUEST, "REVIEW4001", "별점은 0점 이상이어야합니다"), REVIEW_DELETE_NOT_OWNER(HttpStatus.FORBIDDEN,"REVIEW4003","리뷰를 삭제할 권한이 없습니다."), + REPORT_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW4004", "리뷰 신고를 찾을 수 없습니다."), // QnA 관련 에러