diff --git a/src/main/java/com/DecodEat/domain/products/controller/ProductController.java b/src/main/java/com/DecodEat/domain/products/controller/ProductController.java index 1b68670..54b316f 100644 --- a/src/main/java/com/DecodEat/domain/products/controller/ProductController.java +++ b/src/main/java/com/DecodEat/domain/products/controller/ProductController.java @@ -103,5 +103,4 @@ public ApiResponse> getRegisterHistor return ApiResponse.onSuccess(productService.getRegisterHistory(user, pageable)); } - } 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 0b41f39..97efa67 100644 --- a/src/main/java/com/DecodEat/domain/products/entity/Product.java +++ b/src/main/java/com/DecodEat/domain/products/entity/Product.java @@ -4,6 +4,7 @@ import com.DecodEat.global.common.BaseEntity; import jakarta.persistence.*; import lombok.*; +import lombok.experimental.SuperBuilder; import java.util.ArrayList; import java.util.List; @@ -12,9 +13,9 @@ @Table(name = "product") @Getter @Setter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -@Builder +@SuperBuilder public class Product extends BaseEntity { @Id diff --git a/src/main/java/com/DecodEat/domain/report/controller/ReportController.java b/src/main/java/com/DecodEat/domain/report/controller/ReportController.java new file mode 100644 index 0000000..d365e24 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/controller/ReportController.java @@ -0,0 +1,44 @@ +package com.DecodEat.domain.report.controller; + +import com.DecodEat.domain.products.dto.response.ProductDetailDto; +import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; +import com.DecodEat.domain.report.dto.response.ReportResponseDto; +import com.DecodEat.domain.report.service.ReportService; +import com.DecodEat.domain.users.entity.User; +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.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/reports") +@Tag(name = "신고") +public class ReportController { + + private final ReportService reportService; + + @Operation( + summary = "상품 정보 업데이트 요청", + description = "사이트의 영양정보가 실제와 다른 경우 수정 요청") + @PostMapping("/nutrition-info") + public ApiResponse requestUpdateNutritionInfo(@CurrentUser User user, + @RequestParam Long productId, + @RequestBody ProductNutritionUpdateRequestDto requestDto) { + + return ApiResponse.onSuccess(reportService.requestUpdateNutrition(user, productId, requestDto)); + } + + @Operation( + summary = "상품 사진 확인 요청", + description = "부적절한 이미지인 경우 확인 요청") + @PostMapping("/image") + public ApiResponse requestCheckImage(@CurrentUser User user, + @RequestParam Long productId, + @RequestParam String imageUrl) { + + return ApiResponse.onSuccess(reportService.requestCheckImage(user, productId, imageUrl)); + } +} diff --git a/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java new file mode 100644 index 0000000..5732be0 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/converter/ReportConverter.java @@ -0,0 +1,47 @@ +package com.DecodEat.domain.report.converter; + +import com.DecodEat.domain.products.entity.Product; +import com.DecodEat.domain.report.dto.request.ProductNutritionUpdateRequestDto; +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.ReportStatus; +import com.DecodEat.domain.users.entity.User; + +public class ReportConverter { + public static ReportResponseDto toReportResponseDto(Long productId, String type){ + return ReportResponseDto.builder() + .productId(productId) + .reportContent(type) + .build(); + } + + public static NutritionReport toNutritionReport(Long reporterId, Product product,ProductNutritionUpdateRequestDto requestDto){ + return NutritionReport.builder() + .product(product) + .reporterId(reporterId) + .reportStatus(ReportStatus.IN_PROGRESS) + .calcium(requestDto.getCalcium()) + .carbohydrate(requestDto.getCarbohydrate()) + .cholesterol(requestDto.getCholesterol()) + .dietaryFiber(requestDto.getDietaryFiber()) + .energy(requestDto.getEnergy()) + .fat(requestDto.getFat()) + .protein(requestDto.getProtein()) + .satFat(requestDto.getSatFat()) + .sodium(requestDto.getSodium()) + .sugar(requestDto.getSugar()) + .transFat(requestDto.getTransFat()) + .build(); + } + + public static ImageReport toImageReport(Long reporterId, Product product, String imageUrl){ + return ImageReport.builder() + .product(product) + .reporterId(reporterId) + .reportStatus(ReportStatus.IN_PROGRESS) + .imageUrl(imageUrl) + .build(); + } + +} diff --git a/src/main/java/com/DecodEat/domain/report/dto/request/ProductNutritionUpdateRequestDto.java b/src/main/java/com/DecodEat/domain/report/dto/request/ProductNutritionUpdateRequestDto.java new file mode 100644 index 0000000..c99de0c --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/dto/request/ProductNutritionUpdateRequestDto.java @@ -0,0 +1,54 @@ +package com.DecodEat.domain.report.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ProductNutritionUpdateRequestDto { + @NotNull(message = "칼슘 정보는 필수입니다.") + @PositiveOrZero(message = "칼슘은 0 이상의 값이어야 합니다.") + private Double calcium; + + @NotNull(message = "탄수화물 정보는 필수입니다.") + @PositiveOrZero(message = "탄수화물은 0 이상의 값이어야 합니다.") + private Double carbohydrate; + + @NotNull(message = "콜레스테롤 정보는 필수입니다.") + @PositiveOrZero(message = "콜레스테롤은 0 이상의 값이어야 합니다.") + private Double cholesterol; + + @NotNull(message = "식이섬유 정보는 필수입니다.") + @PositiveOrZero(message = "식이섬유는 0 이상의 값이어야 합니다.") + private Double dietaryFiber; + + @NotNull(message = "열량 정보는 필수입니다.") + @PositiveOrZero(message = "열량은 0 이상의 값이어야 합니다.") + private Double energy; + + @NotNull(message = "지방 정보는 필수입니다.") + @PositiveOrZero(message = "지방은 0 이상의 값이어야 합니다.") + private Double fat; + + @NotNull(message = "단백질 정보는 필수입니다.") + @PositiveOrZero(message = "단백질은 0 이상의 값이어야 합니다.") + private Double protein; + + @NotNull(message = "포화지방 정보는 필수입니다.") + @PositiveOrZero(message = "포화지방은 0 이상의 값이어야 합니다.") + private Double satFat; + + @NotNull(message = "나트륨 정보는 필수입니다.") + @PositiveOrZero(message = "나트륨은 0 이상의 값이어야 합니다.") + private Double sodium; + + @NotNull(message = "당류 정보는 필수입니다.") + @PositiveOrZero(message = "당류는 0 이상의 값이어야 합니다.") + private Double sugar; + + @NotNull(message = "트랜스지방 정보는 필수입니다.") + @PositiveOrZero(message = "트랜스지방은 0 이상의 값이어야 합니다.") + private Double transFat; +} 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 new file mode 100644 index 0000000..607c310 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/dto/response/ReportResponseDto.java @@ -0,0 +1,23 @@ +package com.DecodEat.domain.report.dto.response; + +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; + +@Getter +@NoArgsConstructor +@Builder +@AllArgsConstructor +public class ReportResponseDto { + + @NotNull + @Schema(name = "제품 id", example = "1") + Long productId; + + @NotNull + @Schema(name = "신고 유형", example = "영양 정보 수정") + String reportContent; +} diff --git a/src/main/java/com/DecodEat/domain/report/entity/ImageReport.java b/src/main/java/com/DecodEat/domain/report/entity/ImageReport.java new file mode 100644 index 0000000..e936828 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/entity/ImageReport.java @@ -0,0 +1,26 @@ +package com.DecodEat.domain.report.entity; + + +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + + +@Entity +@Table(name = "image_reports") +@Getter +@DiscriminatorValue("INAPPROPRIATE_IMAGE") +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ImageReport extends ReportRecord { + + @Column(name = "image_url") + private String imageUrl; + +} diff --git a/src/main/java/com/DecodEat/domain/report/entity/NutritionReport.java b/src/main/java/com/DecodEat/domain/report/entity/NutritionReport.java new file mode 100644 index 0000000..86a67ec --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/entity/NutritionReport.java @@ -0,0 +1,49 @@ +package com.DecodEat.domain.report.entity; + +import com.DecodEat.domain.products.entity.Product; +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Entity +@Table(name = "nutrition_reports") +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("NUTRITION_UPDATE") +public class NutritionReport extends ReportRecord { + + @Column(name = "calcium") + private Double calcium; + + @Column(name = "carbohydrate") + private Double carbohydrate; + + @Column(name = "cholesterol") + private Double cholesterol; + + @Column(name = "dietary_fiber") + private Double dietaryFiber; + + @Column(name = "energy") + private Double energy; + + @Column(name = "fat") + private Double fat; + + @Column(name = "protein") + private Double protein; + + @Column(name = "sat_fat") + private Double satFat; + + @Column(name = "sodium") + private Double sodium; + + @Column(name = "sugar") + private Double sugar; + + @Column(name = "trans_fat") + private Double transFat; +} diff --git a/src/main/java/com/DecodEat/domain/report/entity/ReportRecord.java b/src/main/java/com/DecodEat/domain/report/entity/ReportRecord.java new file mode 100644 index 0000000..219625d --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/entity/ReportRecord.java @@ -0,0 +1,35 @@ +package com.DecodEat.domain.report.entity; + +import com.DecodEat.domain.products.entity.Product; +import com.DecodEat.global.common.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.*; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Entity +@Getter +@SuperBuilder // 자식 클래스에서 부모 클래스 필드까지 빌드(컴파일 시점에) <-> @Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 서비스 코드 등 다른 곳에서 의미 없는 빈 객체를 실수로 생성하는 것 예방 +@Table(name = "report_records") +@Inheritance(strategy = InheritanceType.JOINED) // JOINED 사용 +@DiscriminatorColumn(name = "REPORT_TYPE") // 자식 엔티티를 구분할 컬럼 (JPA가 생성) +public abstract class ReportRecord extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @Column(nullable = false) + private Long reporterId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + @Schema(name = "처리 상태", example = "IN_PROGRESS") + private ReportStatus reportStatus; +} diff --git a/src/main/java/com/DecodEat/domain/report/entity/ReportStatus.java b/src/main/java/com/DecodEat/domain/report/entity/ReportStatus.java new file mode 100644 index 0000000..529c101 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/entity/ReportStatus.java @@ -0,0 +1,7 @@ +package com.DecodEat.domain.report.entity; + +public enum ReportStatus { + IN_PROGRESS, + UPDATED, + CHECKED +} diff --git a/src/main/java/com/DecodEat/domain/report/repository/ImageReportRepository.java b/src/main/java/com/DecodEat/domain/report/repository/ImageReportRepository.java new file mode 100644 index 0000000..117d2f0 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/repository/ImageReportRepository.java @@ -0,0 +1,12 @@ +package com.DecodEat.domain.report.repository; + +import com.DecodEat.domain.report.entity.ImageReport; +import com.DecodEat.domain.report.entity.NutritionReport; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.awt.*; + +@Repository +public interface ImageReportRepository extends JpaRepository { +} diff --git a/src/main/java/com/DecodEat/domain/report/repository/NutritionReportRepository.java b/src/main/java/com/DecodEat/domain/report/repository/NutritionReportRepository.java new file mode 100644 index 0000000..8f54010 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/repository/NutritionReportRepository.java @@ -0,0 +1,10 @@ +package com.DecodEat.domain.report.repository; + +import com.DecodEat.domain.report.entity.NutritionReport; +import com.DecodEat.domain.report.entity.ReportRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface NutritionReportRepository extends JpaRepository { +} diff --git a/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java b/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java new file mode 100644 index 0000000..18c5c73 --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/repository/ReportRecordRepository.java @@ -0,0 +1,9 @@ +package com.DecodEat.domain.report.repository; + +import com.DecodEat.domain.report.entity.ReportRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReportRecordRepository extends JpaRepository { +} diff --git a/src/main/java/com/DecodEat/domain/report/service/ReportService.java b/src/main/java/com/DecodEat/domain/report/service/ReportService.java new file mode 100644 index 0000000..3914d7d --- /dev/null +++ b/src/main/java/com/DecodEat/domain/report/service/ReportService.java @@ -0,0 +1,44 @@ +package com.DecodEat.domain.report.service; + +import com.DecodEat.domain.products.entity.Product; +import com.DecodEat.domain.products.repository.ProductRepository; +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.repository.ImageReportRepository; +import com.DecodEat.domain.report.repository.NutritionReportRepository; +import com.DecodEat.domain.users.entity.User; +import com.DecodEat.global.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.DecodEat.global.apiPayload.code.status.ErrorStatus.*; + +@Service +@RequiredArgsConstructor +@Transactional +public class ReportService { + private final ProductRepository productRepository; + private final NutritionReportRepository nutritionReportRepository; + private final ImageReportRepository imageReportRepository; + + public ReportResponseDto requestUpdateNutrition(User user, Long productId, ProductNutritionUpdateRequestDto requestDto){ + + Product productProxy = productRepository.getReferenceById(productId); //SELECT 쿼리 없이 ID만 가진 프록시 객체를 가져옴 + + nutritionReportRepository.save(ReportConverter.toNutritionReport(user.getId(), productProxy, requestDto)); + + return ReportConverter.toReportResponseDto(productId,"상품 정보 업데이트 요청 완료"); + } + + public ReportResponseDto requestCheckImage(User user, Long productId, String imageUrl){ + + Product productProxy = productRepository.getReferenceById(productId); //SELECT 쿼리 없이 ID만 가진 프록시 객체를 가져옴 + + imageReportRepository.save(ReportConverter.toImageReport(user.getId(), productProxy, imageUrl)); + + return ReportConverter.toReportResponseDto(productId,"상품 사진 확인 요청 완료"); + } + +} diff --git a/src/main/java/com/DecodEat/domain/users/entity/User.java b/src/main/java/com/DecodEat/domain/users/entity/User.java index c6fa2ca..d0d7dbb 100644 --- a/src/main/java/com/DecodEat/domain/users/entity/User.java +++ b/src/main/java/com/DecodEat/domain/users/entity/User.java @@ -2,16 +2,14 @@ import com.DecodEat.global.common.BaseEntity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; +import lombok.experimental.SuperBuilder; @Entity @Getter -@Builder -@NoArgsConstructor +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Table(name = "users") public class User extends BaseEntity { diff --git a/src/main/java/com/DecodEat/global/common/BaseEntity.java b/src/main/java/com/DecodEat/global/common/BaseEntity.java index 97dfaf6..b74611c 100644 --- a/src/main/java/com/DecodEat/global/common/BaseEntity.java +++ b/src/main/java/com/DecodEat/global/common/BaseEntity.java @@ -2,7 +2,10 @@ import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -12,6 +15,8 @@ @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseEntity { @CreatedDate