From 809e2e828736e0318caa855e2d8e7b74dadf21ee Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Sat, 20 Sep 2025 16:25:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=97=90=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/products/converter/ProductConverter.java | 4 +++- .../domain/products/dto/response/ProductDetailDto.java | 2 ++ .../domain/products/repository/ProductLikeRepository.java | 2 ++ .../DecodEat/domain/products/service/ProductService.java | 7 ++++++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java b/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java index 726df7b..0ee8a8e 100644 --- a/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java +++ b/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java @@ -17,7 +17,8 @@ public class ProductConverter { public static ProductDetailDto toProductDetailDto(Product product, List productInfoImageUrls , - ProductNutrition productNutrition) { + ProductNutrition productNutrition, + boolean isLiked) { Map> nutrientsMap = product.getIngredients().stream() .collect(Collectors.groupingBy( @@ -35,6 +36,7 @@ public static ProductDetailDto toProductDetailDto(Product product, .name(product.getProductName()) .manufacturer(product.getManufacturer()) .productImage(product.getProductImage()) + .isLiked(isLiked) .calcium(productNutrition.getCalcium()) .carbohydrate(productNutrition.getCarbohydrate()) .cholesterol(productNutrition.getCholesterol()) diff --git a/src/main/java/com/DecodEat/domain/products/dto/response/ProductDetailDto.java b/src/main/java/com/DecodEat/domain/products/dto/response/ProductDetailDto.java index 1efda5a..5a70fb1 100644 --- a/src/main/java/com/DecodEat/domain/products/dto/response/ProductDetailDto.java +++ b/src/main/java/com/DecodEat/domain/products/dto/response/ProductDetailDto.java @@ -25,6 +25,8 @@ public class ProductDetailDto { private List imageUrl; + private boolean isLiked; + private Double calcium; private Double carbohydrate; private Double cholesterol; diff --git a/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java b/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java index 779ca71..484afa6 100644 --- a/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java +++ b/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java @@ -11,4 +11,6 @@ public interface ProductLikeRepository extends JpaRepository Optional findByUserAndProduct(User user, Product product); + boolean existsByUserAndProduct(User user, Product product); + } diff --git a/src/main/java/com/DecodEat/domain/products/service/ProductService.java b/src/main/java/com/DecodEat/domain/products/service/ProductService.java index cfe0bcf..325743c 100644 --- a/src/main/java/com/DecodEat/domain/products/service/ProductService.java +++ b/src/main/java/com/DecodEat/domain/products/service/ProductService.java @@ -62,7 +62,12 @@ public ProductDetailDto getDetail(Long id, User user) { ProductNutrition productNutrition = productNutritionRepository.findByProduct(product).orElseThrow(() -> new GeneralException(PRODUCT_NUTRITION_NOT_EXISTED)); - return ProductConverter.toProductDetailDto(product, imageUrls, productNutrition); + // 좋아요 여부 확인 + boolean isLiked = false; + if(user != null){ + isLiked = productLikeRepository.existsByUserAndProduct(user,product); + } + return ProductConverter.toProductDetailDto(product, imageUrls, productNutrition, isLiked); } public ProductRegisterResponseDto addProduct(User user, ProductRegisterRequestDto requestDto, MultipartFile productImage, List productInfoImages) { From b99564faa8a918d7dd3beab456ea2d55d800f346 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Sat, 20 Sep 2025 19:31:43 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProductController.java | 4 ++-- .../products/converter/ProductConverter.java | 8 ++++--- .../dto/response/ProductResponseDTO.java | 3 +++ .../repository/ProductLikeRepository.java | 10 ++++++++ .../products/service/ProductService.java | 23 ++++++++++++++----- 5 files changed, 37 insertions(+), 11 deletions(-) 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 074acd8..842b92f 100644 --- a/src/main/java/com/DecodEat/domain/products/controller/ProductController.java +++ b/src/main/java/com/DecodEat/domain/products/controller/ProductController.java @@ -67,8 +67,8 @@ public ApiResponse registerProduct( ) @GetMapping("/latest") public ApiResponse getProductList( - @RequestParam(required = false) Long cursorId) { - return ApiResponse.onSuccess(productService.getProducts(cursorId)); + @RequestParam(required = false) Long cursorId, @OptionalUser User user) { + return ApiResponse.onSuccess(productService.getProducts(cursorId, user)); } @GetMapping("/search/autocomplete") diff --git a/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java b/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java index 0ee8a8e..ae06fc7 100644 --- a/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java +++ b/src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static com.DecodEat.domain.products.entity.RawMaterial.RawMaterialCategory.*; @@ -69,13 +70,14 @@ public static ProductRegisterResponseDto toProductRegisterDto(Product product, L } // 단일 Product → ProductListItemDTO 변환 - public static ProductResponseDTO.ProductListItemDTO toProductListItemDTO(Product product){ + public static ProductResponseDTO.ProductListItemDTO toProductListItemDTO(Product product, boolean isLiked) { return ProductResponseDTO.ProductListItemDTO.builder() .productId(product.getProductId()) .manufacturer(product.getManufacturer()) .productName(product.getProductName()) .productImage(product.getProductImage()) .decodeStatus(product.getDecodeStatus()) + .isLiked(isLiked) .build(); } @@ -107,9 +109,9 @@ public static ProductRegisterHistoryDto toProductRegisterHistoryDto(Product prod // Slice → ProductListResultDTO 변환 - public static ProductResponseDTO.ProductListResultDTO toProductListResultDTO(Slice slice) { + public static ProductResponseDTO.ProductListResultDTO toProductListResultDTO(Slice slice, Set likedProductIds) { List productList = slice.getContent().stream() - .map(ProductConverter::toProductListItemDTO) + .map(product -> toProductListItemDTO(product, likedProductIds.contains(product.getProductId()))) .toList(); Long nextCursorId = (slice.hasNext() && !productList.isEmpty()) diff --git a/src/main/java/com/DecodEat/domain/products/dto/response/ProductResponseDTO.java b/src/main/java/com/DecodEat/domain/products/dto/response/ProductResponseDTO.java index 6a951ce..dd9d837 100644 --- a/src/main/java/com/DecodEat/domain/products/dto/response/ProductResponseDTO.java +++ b/src/main/java/com/DecodEat/domain/products/dto/response/ProductResponseDTO.java @@ -55,6 +55,9 @@ public static class ProductListItemDTO { @Schema(description = "뷴석 상태", example = "COMPLETED") private DecodeStatus decodeStatus; + + @Schema(description = "좋아요 여부", example = "true") + private boolean isLiked; } } diff --git a/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java b/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java index 484afa6..088afc1 100644 --- a/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java +++ b/src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java @@ -4,13 +4,23 @@ import com.DecodEat.domain.products.entity.ProductLike; import com.DecodEat.domain.users.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface ProductLikeRepository extends JpaRepository { Optional findByUserAndProduct(User user, Product product); + // 여러 제품에 대한 좋아요 여부 조회 ( 제품 리스트 조회시 N + 1 문제 해결) + @Query("SELECT pl.product.productId " + + "FROM ProductLike pl " + + "WHERE pl.user = :user AND pl.product.productId IN :productIds") + List findLikedProductIdsByUserAndProductIds(@Param("user") User user, + @Param("productIds") List productIds); + boolean existsByUserAndProduct(User user, Product product); } diff --git a/src/main/java/com/DecodEat/domain/products/service/ProductService.java b/src/main/java/com/DecodEat/domain/products/service/ProductService.java index 325743c..1e92ce5 100644 --- a/src/main/java/com/DecodEat/domain/products/service/ProductService.java +++ b/src/main/java/com/DecodEat/domain/products/service/ProductService.java @@ -27,10 +27,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.swing.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import static com.DecodEat.global.apiPayload.code.status.ErrorStatus.*; @@ -115,11 +112,25 @@ public ProductRegisterResponseDto addProduct(User user, ProductRegisterRequestDt } @Transactional(readOnly = true) - public ProductResponseDTO.ProductListResultDTO getProducts(Long cursorId) { + public ProductResponseDTO.ProductListResultDTO getProducts(Long cursorId, User user) { Pageable pageable = PageRequest.of(0, PAGE_SIZE); Slice slice = productRepository.findCompletedProductsByCursor(cursorId, pageable); - return ProductConverter.toProductListResultDTO(slice); + // 기본값: 전부 false + Set likedProductIds = Collections.emptySet(); + + // user가 null이 아닐 때만 DB에서 좋아요 여부 조회 + if (user != null && !slice.isEmpty()) { + List productIds = slice.getContent().stream() + .map(Product::getProductId) + .toList(); + + likedProductIds = new HashSet<>( + productLikeRepository.findLikedProductIdsByUserAndProductIds(user, productIds) + ); + } + + return ProductConverter.toProductListResultDTO(slice, likedProductIds); } // todo: 검색은 상품 엔티티와 1:1 매핑 불가능 -> userbehavior 어떻게?