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 726df7b..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.*; @@ -17,7 +18,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 +37,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()) @@ -67,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(); } @@ -105,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/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/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 779ca71..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,11 +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 cfe0bcf..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.*; @@ -62,7 +59,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) { @@ -110,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 어떻게?