diff --git a/src/main/java/com/DecodEat/domain/report/controller/ReportController.java b/src/main/java/com/DecodEat/domain/report/controller/ReportController.java index d365e24..9f32e64 100644 --- a/src/main/java/com/DecodEat/domain/report/controller/ReportController.java +++ b/src/main/java/com/DecodEat/domain/report/controller/ReportController.java @@ -8,10 +8,14 @@ import com.DecodEat.global.apiPayload.ApiResponse; import com.DecodEat.global.common.annotation.CurrentUser; 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.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/api/reports") @@ -41,4 +45,18 @@ public ApiResponse requestCheckImage(@CurrentUser User user, return ApiResponse.onSuccess(reportService.requestCheckImage(user, productId, imageUrl)); } + @Operation( + summary = "상품 수정 요청 조회 (관리자)", + description = "관리자가 모든 상품 정보 수정 요청을 페이지별로 조회합니다. 영양 정보 수정과 이미지 확인 요청을 모두 포함합니다.") + @Parameters({ + @Parameter(name = "page", description = "페이지 번호, 0부터 시작합니다.", example = "0"), + @Parameter(name = "size", description = "한 페이지에 보여줄 항목 수", example = "10") + }) + @GetMapping + public ApiResponse getReports( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + return ApiResponse.onSuccess(reportService.getReports(page, size)); + } } 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 5732be0..23c2f65 100644 --- a/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java +++ b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java @@ -5,8 +5,12 @@ import com.DecodEat.domain.report.dto.response.ReportResponseDto; import com.DecodEat.domain.report.entity.ImageReport; import com.DecodEat.domain.report.entity.NutritionReport; +import com.DecodEat.domain.report.entity.ReportRecord; import com.DecodEat.domain.report.entity.ReportStatus; import com.DecodEat.domain.users.entity.User; +import org.springframework.data.domain.Page; + +import java.util.stream.Collectors; public class ReportConverter { public static ReportResponseDto toReportResponseDto(Long productId, String type){ @@ -44,4 +48,57 @@ public static ImageReport toImageReport(Long reporterId, Product product, String .build(); } + // 영양 정보 신고 내용 매핑 + public static ReportResponseDto.ReportedNutritionInfo toReportedNutritionInfo(NutritionReport nutritionReport){ + return ReportResponseDto.ReportedNutritionInfo.builder() + .calcium(nutritionReport.getCalcium()) + .carbohydrate(nutritionReport.getCarbohydrate()) + .cholesterol(nutritionReport.getCholesterol()) + .dietaryFiber(nutritionReport.getDietaryFiber()) + .energy(nutritionReport.getEnergy()) + .fat(nutritionReport.getFat()) + .protein(nutritionReport.getProtein()) + .satFat(nutritionReport.getSatFat()) + .sodium(nutritionReport.getSodium()) + .sugar(nutritionReport.getSugar()) + .transFat(nutritionReport.getTransFat()) + .build(); + } + + public static ReportResponseDto.ReportListItemDTO toReportListItemDTO(ReportRecord reportRecord){ + ReportResponseDto.ReportListItemDTO.ReportListItemDTOBuilder builder = ReportResponseDto.ReportListItemDTO.builder() + .reportId(reportRecord.getId()) + .reporterId(reportRecord.getReporterId()) + .productId(reportRecord.getProduct().getProductId()) + .productName(reportRecord.getProduct().getProductName()) + .reportStatus(reportRecord.getReportStatus()) + .createdAt(reportRecord.getCreatedAt()); + + // 영양정보 관련인지 이미지 관련인지 구분 + if(reportRecord instanceof NutritionReport){ + NutritionReport nutritionReport = (NutritionReport) reportRecord; + builder.reportType("NUTRITION_UPDATE") + .nutritionRequestInfo(toReportedNutritionInfo(nutritionReport)); + + } else if (reportRecord instanceof ImageReport) { + ImageReport imageReport = (ImageReport) reportRecord; + builder.reportType("INAPPROPRIATE_IMAGE") + .imageUrl(imageReport.getImageUrl()); + } + + return builder.build(); + } + public static ReportResponseDto.ReportListResponseDTO toReportListResponseDTO(Page reportPage) { + return ReportResponseDto.ReportListResponseDTO.builder() + // 1. 신고 목록 변환 + .reportList(reportPage.getContent().stream() + .map(ReportConverter::toReportListItemDTO) + .collect(Collectors.toList())) + // 2. 페이징 정보 매핑 + .totalPage(reportPage.getTotalPages()) + .totalElements(reportPage.getTotalElements()) + .isFirst(reportPage.isFirst()) + .isLast(reportPage.isLast()) + .build(); + } } 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 607c310..f121302 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,11 +1,15 @@ package com.DecodEat.domain.report.dto.response; +import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; +import com.DecodEat.domain.report.entity.ReportStatus; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; +import java.time.LocalDateTime; @Getter @NoArgsConstructor @@ -20,4 +24,84 @@ public class ReportResponseDto { @NotNull @Schema(name = "신고 유형", example = "영양 정보 수정") String reportContent; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "신고된 영양정보 내용") + public static class ReportedNutritionInfo { + 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 + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "관리자용 신고 리스트 아이템") + public static class ReportListItemDTO { + + @Schema(description = "신고 ID", example = "1") + private Long reportId; + + @Schema(description = "신고자 ID", example = "2") + private Long reporterId; + + @Schema(description = "상품 ID", example = "13") + private Long productId; + + @Schema(description = "상품명", example = "맛있는 사과") + private String productName; + + @Schema(description = "신고 유형", example = "NUTRITION_UPDATE") + private String reportType; + + @Schema(description = "처리 상태", example = "IN_PROGRESS") + private ReportStatus reportStatus; + + @Schema(description = "신고일") + private LocalDateTime createdAt; + + @Schema(description = "신고된 이미지 URL (이미지 신고인 경우)", example = "http://example.com/inappropriate.jpg", nullable = true) + private String imageUrl; + + @Schema(description = "신고된 영양정보 수정 요청 내용 (영양정보 신고인 경우)", nullable = true) + private ReportedNutritionInfo nutritionRequestInfo; + + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "관리자용 신고 리스트") + public static class ReportListResponseDTO { + + @Schema(description = "신고 내역 목록") + private List reportList; + + @Schema(description = "총 페이지 수") + private Integer totalPage; + + @Schema(description = "총 신고 내역 수") + private Long totalElements; + + @Schema(description = "마지막 페이지 여부") + private Boolean isLast; + + @Schema(description = "첫 페이지 여부") + private Boolean isFirst; + } + } 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 3914d7d..b99e9a8 100644 --- a/src/main/java/com/DecodEat/domain/report/service/ReportService.java +++ b/src/main/java/com/DecodEat/domain/report/service/ReportService.java @@ -5,11 +5,17 @@ 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.repository.ImageReportRepository; 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.exception.GeneralException; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +28,7 @@ public class ReportService { private final ProductRepository productRepository; private final NutritionReportRepository nutritionReportRepository; private final ImageReportRepository imageReportRepository; + private final ReportRecordRepository reportRecordRepository; public ReportResponseDto requestUpdateNutrition(User user, Long productId, ProductNutritionUpdateRequestDto requestDto){ @@ -41,4 +48,11 @@ 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")); + Page reportPage = reportRecordRepository.findAll(pageable); + return ReportConverter.toReportListResponseDTO(reportPage); + } + } diff --git a/src/main/java/com/DecodEat/global/config/CorsConfig.java b/src/main/java/com/DecodEat/global/config/CorsConfig.java index e425136..7c369dd 100644 --- a/src/main/java/com/DecodEat/global/config/CorsConfig.java +++ b/src/main/java/com/DecodEat/global/config/CorsConfig.java @@ -16,7 +16,7 @@ public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOriginPatterns(List.of( "https://decodeat.netlify.app", - "http://localhost:8080","http://localhost:5173" )); + "http://localhost:8080","http://localhost:5173", "http://decodeat.store", "https://decodeat.store" )); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); // 쿠키/인증정보 포함 허용