From 5bc3bde941b32809dc44637df22647ed729370b5 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Fri, 12 Sep 2025 16:18:11 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Refactor:=20=EC=8B=A0=EA=B3=A0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=8B=9C=20=EC=83=81=ED=92=88=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=8F=84=20=EA=B0=99=EC=9D=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/products/entity/Product.java | 8 ++++ .../controller/AdminReportController.java | 13 ++++++ .../report/converter/ReportConverter.java | 45 ++++++++++++++++++- .../dto/response/ReportResponseDto.java | 45 ++++++++++++++++--- .../repository/ReportRecordRepository.java | 19 ++++++++ .../domain/report/service/ReportService.java | 31 ++++++++++++- 6 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/DecodEat/domain/products/entity/Product.java b/src/main/java/com/DecodEat/domain/products/entity/Product.java index 97efa67..a2f7dd0 100644 --- a/src/main/java/com/DecodEat/domain/products/entity/Product.java +++ b/src/main/java/com/DecodEat/domain/products/entity/Product.java @@ -43,4 +43,12 @@ public class Product extends BaseEntity { @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @Builder.Default // 원재료 리스트 private List ingredients = new ArrayList<>(); + + @OneToOne(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + private ProductNutrition productNutrition; + + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + private List infoImages = new ArrayList<>(); + } \ No newline at end of file diff --git a/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java b/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java index 2e0eb49..32f2db0 100644 --- a/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java +++ b/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java @@ -43,4 +43,17 @@ public ApiResponse getReports( public ApiResponse rejectReport(@PathVariable Long reportId) { return ApiResponse.onSuccess(reportService.rejectReport(reportId)); } + + @Operation( + summary = "상품 수정 요청 수락 (관리자)", + description = """ + 관리자가 상품 정보 수정 요청을 수락합니다. 해당 신고 내역의 상태를 ACCEPTED로 변경합니다. + - **영양 정보 신고 수락 시:** 실제 상품의 영양 정보가 신고된 내용으로 업데이트됩니다. + - **부적절 이미지 신고 수락 시:** 해당 상품이 데이터베이스에서 삭제됩니다.""") + @Parameter(name = "reportId", description = "수럭할 신고의 ID", example = "1") + // @PreAuthorize("hasRole('ADMIN')") // Spring Security 사용 시 권한 설정 + @PatchMapping("/{reportId}/accept") + public ApiResponse acceptReport(@PathVariable Long reportId) { + return ApiResponse.onSuccess(reportService.acceptReport(reportId)); + } } diff --git a/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java index 23c2f65..b3445fa 100644 --- a/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java +++ b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java @@ -1,6 +1,8 @@ package com.DecodEat.domain.report.converter; import com.DecodEat.domain.products.entity.Product; +import com.DecodEat.domain.products.entity.ProductInfoImage; +import com.DecodEat.domain.products.entity.ProductNutrition; import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; import com.DecodEat.domain.report.dto.response.ReportResponseDto; import com.DecodEat.domain.report.entity.ImageReport; @@ -10,6 +12,7 @@ import com.DecodEat.domain.users.entity.User; import org.springframework.data.domain.Page; +import java.util.List; import java.util.stream.Collectors; public class ReportConverter { @@ -65,12 +68,50 @@ public static ReportResponseDto.ReportedNutritionInfo toReportedNutritionInfo(Nu .build(); } + public static ReportResponseDto.SimpleProductInfoDTO toSimpleProductInfoDTO(Product product) { + List infoImageUrls = product.getInfoImages().stream() + .map(ProductInfoImage::getImageUrl) + .collect(Collectors.toList()); + + return ReportResponseDto.SimpleProductInfoDTO.builder() + .productId(product.getProductId()) + .productName(product.getProductName()) + .manufacturer(product.getManufacturer()) + .productImage(product.getProductImage()) + .infoImageUrls(infoImageUrls) + .build(); + } + public static ReportResponseDto.ProductNutritionInfoDTO toProductNutritionInfoDTO(ProductNutrition nutrition) { + // 상품에 아직 영양 정보가 등록되지 않은 경우 + if (nutrition == null) { + return null; + } + + return ReportResponseDto.ProductNutritionInfoDTO.builder() + .calcium(nutrition.getCalcium()) + .carbohydrate(nutrition.getCarbohydrate()) + .cholesterol(nutrition.getCholesterol()) + .dietaryFiber(nutrition.getDietaryFiber()) + .energy(nutrition.getEnergy()) + .fat(nutrition.getFat()) + .protein(nutrition.getProtein()) + .satFat(nutrition.getSatFat()) + .sodium(nutrition.getSodium()) + .sugar(nutrition.getSugar()) + .transFat(nutrition.getTransFat()) + .build(); + } + public static ReportResponseDto.ReportListItemDTO toReportListItemDTO(ReportRecord reportRecord){ + + Product product = reportRecord.getProduct(); + ProductNutrition currentNutrition = product.getProductNutrition(); + ReportResponseDto.ReportListItemDTO.ReportListItemDTOBuilder builder = ReportResponseDto.ReportListItemDTO.builder() .reportId(reportRecord.getId()) .reporterId(reportRecord.getReporterId()) - .productId(reportRecord.getProduct().getProductId()) - .productName(reportRecord.getProduct().getProductName()) + .productInfo(toSimpleProductInfoDTO(product)) + .currentNutritionInfo(toProductNutritionInfoDTO(currentNutrition)) .reportStatus(reportRecord.getReportStatus()) .createdAt(reportRecord.getCreatedAt()); diff --git a/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java b/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java index f121302..31e98e7 100644 --- a/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java +++ b/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java @@ -1,6 +1,9 @@ package com.DecodEat.domain.report.dto.response; +import com.DecodEat.domain.products.entity.ProductNutrition; +import com.DecodEat.domain.products.entity.ProductRawMaterial; import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; +import com.DecodEat.domain.report.entity.ImageReport; import com.DecodEat.domain.report.entity.ReportStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; @@ -44,6 +47,38 @@ public static class ReportedNutritionInfo { private Double transFat; } + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "신고된 상품의 간략 정보") + public static class SimpleProductInfoDTO { + private Long productId; + private String productName; + private String manufacturer; + private String productImage; + private List infoImageUrls; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "상품의 현재 영양 정보") + public static class ProductNutritionInfoDTO { + private Double calcium; + private Double carbohydrate; + private Double cholesterol; + private Double dietaryFiber; + private Double energy; + private Double fat; + private Double protein; + private Double satFat; + private Double sodium; + private Double sugar; + private Double transFat; + } + @Getter @Builder @@ -58,11 +93,8 @@ public static class ReportListItemDTO { @Schema(description = "신고자 ID", example = "2") private Long reporterId; - @Schema(description = "상품 ID", example = "13") - private Long productId; - - @Schema(description = "상품명", example = "맛있는 사과") - private String productName; + @Schema(description = "신고된 상품 정보") + private SimpleProductInfoDTO productInfo; @Schema(description = "신고 유형", example = "NUTRITION_UPDATE") private String reportType; @@ -79,6 +111,9 @@ public static class ReportListItemDTO { @Schema(description = "신고된 영양정보 수정 요청 내용 (영양정보 신고인 경우)", nullable = true) private ReportedNutritionInfo nutritionRequestInfo; + @Schema(description = "상품의 현재 DB에 저장된 영양 정보", nullable = true) + private ProductNutritionInfoDTO currentNutritionInfo; + } @Getter diff --git a/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java b/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java index 18c5c73..6ded5ed 100644 --- a/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java +++ b/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java @@ -1,9 +1,28 @@ package com.DecodEat.domain.report.repository; import com.DecodEat.domain.report.entity.ReportRecord; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface ReportRecordRepository extends JpaRepository { + + // 영양정보 전체 조회 + @Query(value = "SELECT r FROM ReportRecord r " + + "JOIN FETCH r.product p " + + "LEFT JOIN FETCH p.productNutrition", + countQuery = "SELECT count(r) FROM ReportRecord r") + Page findAllWithDetails(Pageable pageable); + + // 단일 조회 + @Query("SELECT r FROM ReportRecord r " + + "JOIN FETCH r.product p " + + "LEFT JOIN FETCH p.productNutrition " + + "WHERE r.id = :id") + Optional findByIdWithDetails(Long id); } diff --git a/src/main/java/com/DecodEat/domain/report/service/ReportService.java b/src/main/java/com/DecodEat/domain/report/service/ReportService.java index b66e5c9..2a1c2d8 100644 --- a/src/main/java/com/DecodEat/domain/report/service/ReportService.java +++ b/src/main/java/com/DecodEat/domain/report/service/ReportService.java @@ -5,8 +5,7 @@ import com.DecodEat.domain.report.converter.ReportConverter; import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; import com.DecodEat.domain.report.dto.response.ReportResponseDto; -import com.DecodEat.domain.report.entity.ReportRecord; -import com.DecodEat.domain.report.entity.ReportStatus; +import com.DecodEat.domain.report.entity.*; import com.DecodEat.domain.report.entity.ReportRecord; import com.DecodEat.domain.report.repository.ImageReportRepository; import com.DecodEat.domain.report.repository.NutritionReportRepository; @@ -73,4 +72,32 @@ public ReportResponseDto rejectReport(Long reportId){ // 3. DTO 반환 return ReportConverter.toReportResponseDto(reportRecord.getProduct().getProductId(), "신고 요청이 거절 처리되었습니다."); } + + + /** + * 상품 수정 신고 요청 수락 + * @param reportId 수락할 신고의 ID + * @return 처리 결과를 담은 DTO + */ + public ReportResponseDto acceptReport(Long reportId){ + // 1. ID로 신고 내역 조회 + ReportRecord reportRecord = reportRecordRepository.findById(reportId) + .orElseThrow(() -> new GeneralException(REPORT_NOT_FOUND)); + + // 2. 이미 처리된 내역인지 확인 + + Product product = reportRecord.getProduct(); + if (reportRecord instanceof NutritionReport) { + NutritionReport nutritionReport = (NutritionReport) reportRecord; +// product.updateNutrition(nutritionReport); + } else if (reportRecord instanceof ImageReport) { + productRepository.delete(product); + } + + // 3. reportstatus 상태를 rejected로 변경 + reportRecord.setReportStatus(ReportStatus.ACCEPTED); + + // 4. DTO 반환 + return ReportConverter.toReportResponseDto(reportRecord.getProduct().getProductId(), "신고 요청이 거절 처리되었습니다."); + } } From 9c418349b6e32c628baa390df2b70b350ab43418 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Mon, 15 Sep 2025 10:48:57 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Feat:=20=EC=83=81=ED=92=88=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=9A=94=EC=B2=AD=20=EC=8B=A0=EA=B3=A0=20=EC=88=98?= =?UTF-8?q?=EB=9D=BD=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/products/entity/Product.java | 7 ++++- .../products/entity/ProductNutrition.java | 19 ++++++++++++ .../controller/AdminReportController.java | 15 +++++++--- .../report/converter/ReportConverter.java | 4 --- .../dto/request/ImageUpdateRequestDto.java | 16 ++++++++++ .../dto/response/ReportResponseDto.java | 4 --- .../domain/report/service/ReportService.java | 30 ++++++++++++++----- .../apiPayload/code/status/ErrorStatus.java | 3 +- 8 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/DecodEat/domain/report/dto/request/ImageUpdateRequestDto.java diff --git a/src/main/java/com/DecodEat/domain/products/entity/Product.java b/src/main/java/com/DecodEat/domain/products/entity/Product.java index a2f7dd0..5be586f 100644 --- a/src/main/java/com/DecodEat/domain/products/entity/Product.java +++ b/src/main/java/com/DecodEat/domain/products/entity/Product.java @@ -48,7 +48,12 @@ public class Product extends BaseEntity { private ProductNutrition productNutrition; @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) - @Builder.Default private List infoImages = new ArrayList<>(); + /** + * 상품의 대표 이미지를 새로운 URL로 업데이트 + */ + public void updateProductImage(String newImageUrl) { + this.productImage = newImageUrl; + } } \ No newline at end of file diff --git a/src/main/java/com/DecodEat/domain/products/entity/ProductNutrition.java b/src/main/java/com/DecodEat/domain/products/entity/ProductNutrition.java index 18db9cd..1c87248 100644 --- a/src/main/java/com/DecodEat/domain/products/entity/ProductNutrition.java +++ b/src/main/java/com/DecodEat/domain/products/entity/ProductNutrition.java @@ -1,5 +1,6 @@ package com.DecodEat.domain.products.entity; +import com.DecodEat.domain.report.entity.NutritionReport; import com.DecodEat.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; @@ -53,4 +54,22 @@ public class ProductNutrition extends BaseEntity { @Column(name = "trans_fat") private Double transFat; + + /** + * 영양 정보 신고 내역을 바탕으로 업데이트 + * @param report 업데이트할 정보가 담긴 NutritionReport 객체 + */ + public void updateFromReport(NutritionReport report) { + this.calcium = report.getCalcium(); + this.carbohydrate = report.getCarbohydrate(); + this.cholesterol = report.getCholesterol(); + this.dietaryFiber = report.getDietaryFiber(); + this.energy = report.getEnergy(); + this.fat = report.getFat(); + this.protein = report.getProtein(); + this.satFat = report.getSatFat(); + this.sodium = report.getSodium(); + this.sugar = report.getSugar(); + this.transFat = report.getTransFat(); + } } \ No newline at end of file diff --git a/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java b/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java index 32f2db0..f2a796a 100644 --- a/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java +++ b/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java @@ -1,11 +1,13 @@ package com.DecodEat.domain.report.controller; +import com.DecodEat.domain.report.dto.request.ImageUpdateRequestDto; import com.DecodEat.domain.report.dto.response.ReportResponseDto; import com.DecodEat.domain.report.service.ReportService; import com.DecodEat.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -49,11 +51,16 @@ public ApiResponse rejectReport(@PathVariable Long reportId) description = """ 관리자가 상품 정보 수정 요청을 수락합니다. 해당 신고 내역의 상태를 ACCEPTED로 변경합니다. - **영양 정보 신고 수락 시:** 실제 상품의 영양 정보가 신고된 내용으로 업데이트됩니다. - - **부적절 이미지 신고 수락 시:** 해당 상품이 데이터베이스에서 삭제됩니다.""") - @Parameter(name = "reportId", description = "수럭할 신고의 ID", example = "1") + - **부적절 이미지 신고 수락 시:** 관리자가 새로운 이미지를 넣으면 해당 이미지로 변경되고, 넣지 않으면 이미지가 삭제됩니다.""") + @Parameters({ + @Parameter(name = "reportId", description = "수락할 신고의 ID", example = "1", required = true) + }) // @PreAuthorize("hasRole('ADMIN')") // Spring Security 사용 시 권한 설정 @PatchMapping("/{reportId}/accept") - public ApiResponse acceptReport(@PathVariable Long reportId) { - return ApiResponse.onSuccess(reportService.acceptReport(reportId)); + public ApiResponse acceptReport( + @PathVariable Long reportId, + @Parameter(name = "imageUpdateRequestDto", description = "이미지 신고 시에만 사용. 새 이미지 URL을 제공하여 교체하거나, 본문을 비워 기존 이미지를 삭제합니다.") + @RequestBody(required = false) ImageUpdateRequestDto requestDto) { + return ApiResponse.onSuccess(reportService.acceptReport(reportId, requestDto)); } } diff --git a/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java index b3445fa..a531367 100644 --- a/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java +++ b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java @@ -82,10 +82,6 @@ public static ReportResponseDto.SimpleProductInfoDTO toSimpleProductInfoDTO(Prod .build(); } public static ReportResponseDto.ProductNutritionInfoDTO toProductNutritionInfoDTO(ProductNutrition nutrition) { - // 상품에 아직 영양 정보가 등록되지 않은 경우 - if (nutrition == null) { - return null; - } return ReportResponseDto.ProductNutritionInfoDTO.builder() .calcium(nutrition.getCalcium()) diff --git a/src/main/java/com/DecodEat/domain/report/dto/request/ImageUpdateRequestDto.java b/src/main/java/com/DecodEat/domain/report/dto/request/ImageUpdateRequestDto.java new file mode 100644 index 0000000..47517c7 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/dto/request/ImageUpdateRequestDto.java @@ -0,0 +1,16 @@ +package com.DecodEat.domain.report.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@Schema(description = "이미지 신고 수락 시, 새 이미지로 교체할 경우 사용하는 DTO") +public class ImageUpdateRequestDto { + + @Schema(description = "새로 등록할 상품의 대표 이미지 URL", + example = "http://example.image.jpg", + nullable = true) + private String newImageUrl; +} \ No newline at end of file diff --git a/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java b/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java index 31e98e7..6dee298 100644 --- a/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java +++ b/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java @@ -1,9 +1,5 @@ package com.DecodEat.domain.report.dto.response; -import com.DecodEat.domain.products.entity.ProductNutrition; -import com.DecodEat.domain.products.entity.ProductRawMaterial; -import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; -import com.DecodEat.domain.report.entity.ImageReport; import com.DecodEat.domain.report.entity.ReportStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/DecodEat/domain/report/service/ReportService.java b/src/main/java/com/DecodEat/domain/report/service/ReportService.java index 2a1c2d8..6412770 100644 --- a/src/main/java/com/DecodEat/domain/report/service/ReportService.java +++ b/src/main/java/com/DecodEat/domain/report/service/ReportService.java @@ -1,8 +1,10 @@ package com.DecodEat.domain.report.service; import com.DecodEat.domain.products.entity.Product; +import com.DecodEat.domain.products.entity.ProductNutrition; import com.DecodEat.domain.products.repository.ProductRepository; import com.DecodEat.domain.report.converter.ReportConverter; +import com.DecodEat.domain.report.dto.request.ImageUpdateRequestDto; import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; import com.DecodEat.domain.report.dto.response.ReportResponseDto; import com.DecodEat.domain.report.entity.*; @@ -66,7 +68,12 @@ public ReportResponseDto rejectReport(Long reportId){ ReportRecord reportRecord = reportRecordRepository.findById(reportId) .orElseThrow(() -> new GeneralException(REPORT_NOT_FOUND)); - // 2. reportstatus 상태를 rejected로 변경 + // 2. 이미 처리된 내역인지 확인 + if(reportRecord.getReportStatus() != ReportStatus.IN_PROGRESS) { + throw new GeneralException(ALREADY_PROCESSED_REPORT); + } + + // 3. reportstatus 상태를 rejected로 변경 reportRecord.setReportStatus(ReportStatus.REJECTED); // 3. DTO 반환 @@ -79,25 +86,34 @@ public ReportResponseDto rejectReport(Long reportId){ * @param reportId 수락할 신고의 ID * @return 처리 결과를 담은 DTO */ - public ReportResponseDto acceptReport(Long reportId){ + public ReportResponseDto acceptReport(Long reportId, ImageUpdateRequestDto requestDto){ // 1. ID로 신고 내역 조회 ReportRecord reportRecord = reportRecordRepository.findById(reportId) .orElseThrow(() -> new GeneralException(REPORT_NOT_FOUND)); // 2. 이미 처리된 내역인지 확인 + if(reportRecord.getReportStatus() != ReportStatus.IN_PROGRESS) { + throw new GeneralException(ALREADY_PROCESSED_REPORT); + } Product product = reportRecord.getProduct(); + + // 3. 신고 유횽에 따른 로직 분기 if (reportRecord instanceof NutritionReport) { - NutritionReport nutritionReport = (NutritionReport) reportRecord; -// product.updateNutrition(nutritionReport); + ProductNutrition productNutrition = product.getProductNutrition(); + productNutrition.updateFromReport((NutritionReport) reportRecord); + } else if (reportRecord instanceof ImageReport) { - productRepository.delete(product); + // 새로운 이미지가 없는 경우 이미지 삭제 -> null로 처리 + // 새로운 이미지가 있는 경우 해당 이미지로 변경 + String newImageUrl = (requestDto != null) ? requestDto.getNewImageUrl() : null; + product.updateProductImage(newImageUrl); } - // 3. reportstatus 상태를 rejected로 변경 + // 4. reportstatus 상태를 accepted 변경 reportRecord.setReportStatus(ReportStatus.ACCEPTED); // 4. DTO 반환 - return ReportConverter.toReportResponseDto(reportRecord.getProduct().getProductId(), "신고 요청이 거절 처리되었습니다."); + return ReportConverter.toReportResponseDto(reportRecord.getProduct().getProductId(), "신고 요청이 수락 처리되었습니다."); } } diff --git a/src/main/java/com/DecodEat/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/DecodEat/global/apiPayload/code/status/ErrorStatus.java index 8cad1d8..9d1ebc0 100644 --- a/src/main/java/com/DecodEat/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/DecodEat/global/apiPayload/code/status/ErrorStatus.java @@ -39,7 +39,8 @@ public enum ErrorStatus implements BaseErrorCode { FILE_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE_001", "파일 업로드에 실패했습니다."), // 신고 - REPORT_NOT_FOUND(HttpStatus.NOT_FOUND, "REPONT_401", "존재하지 않는 신고 내역 입니다."); + REPORT_NOT_FOUND(HttpStatus.NOT_FOUND, "REPORT_401", "존재하지 않는 신고 내역 입니다."), + ALREADY_PROCESSED_REPORT(HttpStatus.CONFLICT, "REPORT_402", "이미 처리된 신고 입니다."); private final HttpStatus httpStatus; private final String code; From 51100bd96f878267112f12bef3bc1421aba39dab Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Mon, 15 Sep 2025 13:55:12 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Refactor:=20=EC=8B=A0=EA=B3=A0=20=EB=82=B4?= =?UTF-8?q?=EC=97=AD=20=EC=A1=B0=ED=9A=8C=20n+1=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/DecodEat/domain/report/service/ReportService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/DecodEat/domain/report/service/ReportService.java b/src/main/java/com/DecodEat/domain/report/service/ReportService.java index 6412770..87962d1 100644 --- a/src/main/java/com/DecodEat/domain/report/service/ReportService.java +++ b/src/main/java/com/DecodEat/domain/report/service/ReportService.java @@ -54,7 +54,7 @@ public ReportResponseDto requestCheckImage(User user, Long productId, String ima @Transactional(readOnly = true) public ReportResponseDto.ReportListResponseDTO getReports(int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); - Page reportPage = reportRecordRepository.findAll(pageable); + Page reportPage = reportRecordRepository.findAllWithDetails(pageable); return ReportConverter.toReportListResponseDTO(reportPage); } From 9d5f256f9a24949d887130cb43b7c310dd3f6937 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Mon, 15 Sep 2025 14:07:49 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=EC=9A=A9=20=EC=8B=A0=EA=B3=A0=20=EB=82=B4=EC=97=AD=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AdminReportController.java | 7 +++++++ .../domain/report/service/ReportService.java | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java b/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java index f2a796a..d1b36b0 100644 --- a/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java +++ b/src/main/java/com/DecodEat/domain/report/controller/AdminReportController.java @@ -63,4 +63,11 @@ public ApiResponse acceptReport( @RequestBody(required = false) ImageUpdateRequestDto requestDto) { return ApiResponse.onSuccess(reportService.acceptReport(reportId, requestDto)); } + + @Operation(summary = "신고 상세 조회 API", description = "관리자가 특정 신고 내역의 상세 정보를 조회합니다.") + @GetMapping("/{reportId}") + public ApiResponse getReportDetetails( + @Parameter(description = "조회할 신고의 ID", example = "1") @PathVariable Long reportId) { + return ApiResponse.onSuccess(reportService.getReportDetails(reportId)); + } } diff --git a/src/main/java/com/DecodEat/domain/report/service/ReportService.java b/src/main/java/com/DecodEat/domain/report/service/ReportService.java index 87962d1..a207239 100644 --- a/src/main/java/com/DecodEat/domain/report/service/ReportService.java +++ b/src/main/java/com/DecodEat/domain/report/service/ReportService.java @@ -13,6 +13,7 @@ import com.DecodEat.domain.report.repository.NutritionReportRepository; import com.DecodEat.domain.report.repository.ReportRecordRepository; import com.DecodEat.domain.users.entity.User; +import com.DecodEat.global.apiPayload.code.status.ErrorStatus; import com.DecodEat.global.exception.GeneralException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -51,6 +52,7 @@ public ReportResponseDto requestCheckImage(User user, Long productId, String ima return ReportConverter.toReportResponseDto(productId,"상품 사진 확인 요청 완료"); } + // 신고 내역 전체 조회 @Transactional(readOnly = true) public ReportResponseDto.ReportListResponseDTO getReports(int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); @@ -58,6 +60,21 @@ public ReportResponseDto.ReportListResponseDTO getReports(int page, int size) { return ReportConverter.toReportListResponseDTO(reportPage); } + /** + * 단일 신고 내역 상세 조회 + * @param reportId 조회할 신고의 ID + * @return 신고 상세 정보를 담은 DTO + */ + @Transactional(readOnly = true) + public ReportResponseDto.ReportListItemDTO getReportDetails(Long reportId) { + + // 1. ID로 신고 내역 조회 + ReportRecord reportRecord = reportRecordRepository.findByIdWithDetails(reportId) + .orElseThrow(() -> new GeneralException(ErrorStatus.REPORT_NOT_FOUND)); + + return ReportConverter.toReportListItemDTO(reportRecord); + } + /** * 상품 수정 신고 요청 거절 * @param reportId 거절할 신고의 ID