Skip to content

Commit e914de2

Browse files
authored
Merge pull request #61 from Decodeat/Feat/#53
Feat: 제품 상세, 홈 화면에 좋아요 여부 추가
2 parents ebf3b3a + b99564f commit e914de2

6 files changed

Lines changed: 50 additions & 13 deletions

File tree

src/main/java/com/DecodEat/domain/products/controller/ProductController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ public ApiResponse<ProductRegisterResponseDto> registerProduct(
6767
)
6868
@GetMapping("/latest")
6969
public ApiResponse<ProductResponseDTO.ProductListResultDTO> getProductList(
70-
@RequestParam(required = false) Long cursorId) {
71-
return ApiResponse.onSuccess(productService.getProducts(cursorId));
70+
@RequestParam(required = false) Long cursorId, @OptionalUser User user) {
71+
return ApiResponse.onSuccess(productService.getProducts(cursorId, user));
7272
}
7373

7474
@GetMapping("/search/autocomplete")

src/main/java/com/DecodEat/domain/products/converter/ProductConverter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@
1010

1111
import java.util.List;
1212
import java.util.Map;
13+
import java.util.Set;
1314
import java.util.stream.Collectors;
1415

1516
import static com.DecodEat.domain.products.entity.RawMaterial.RawMaterialCategory.*;
1617

1718
public class ProductConverter {
1819
public static ProductDetailDto toProductDetailDto(Product product,
1920
List<String> productInfoImageUrls ,
20-
ProductNutrition productNutrition) {
21+
ProductNutrition productNutrition,
22+
boolean isLiked) {
2123
Map<RawMaterialCategory, List<String>> nutrientsMap =
2224
product.getIngredients().stream()
2325
.collect(Collectors.groupingBy(
@@ -35,6 +37,7 @@ public static ProductDetailDto toProductDetailDto(Product product,
3537
.name(product.getProductName())
3638
.manufacturer(product.getManufacturer())
3739
.productImage(product.getProductImage())
40+
.isLiked(isLiked)
3841
.calcium(productNutrition.getCalcium())
3942
.carbohydrate(productNutrition.getCarbohydrate())
4043
.cholesterol(productNutrition.getCholesterol())
@@ -67,13 +70,14 @@ public static ProductRegisterResponseDto toProductRegisterDto(Product product, L
6770
}
6871

6972
// 단일 Product → ProductListItemDTO 변환
70-
public static ProductResponseDTO.ProductListItemDTO toProductListItemDTO(Product product){
73+
public static ProductResponseDTO.ProductListItemDTO toProductListItemDTO(Product product, boolean isLiked) {
7174
return ProductResponseDTO.ProductListItemDTO.builder()
7275
.productId(product.getProductId())
7376
.manufacturer(product.getManufacturer())
7477
.productName(product.getProductName())
7578
.productImage(product.getProductImage())
7679
.decodeStatus(product.getDecodeStatus())
80+
.isLiked(isLiked)
7781
.build();
7882
}
7983

@@ -105,9 +109,9 @@ public static ProductRegisterHistoryDto toProductRegisterHistoryDto(Product prod
105109

106110

107111
// Slice<Product> → ProductListResultDTO 변환
108-
public static ProductResponseDTO.ProductListResultDTO toProductListResultDTO(Slice<Product> slice) {
112+
public static ProductResponseDTO.ProductListResultDTO toProductListResultDTO(Slice<Product> slice, Set<Long> likedProductIds) {
109113
List<ProductResponseDTO.ProductListItemDTO> productList = slice.getContent().stream()
110-
.map(ProductConverter::toProductListItemDTO)
114+
.map(product -> toProductListItemDTO(product, likedProductIds.contains(product.getProductId())))
111115
.toList();
112116

113117
Long nextCursorId = (slice.hasNext() && !productList.isEmpty())

src/main/java/com/DecodEat/domain/products/dto/response/ProductDetailDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class ProductDetailDto {
2525

2626
private List<String> imageUrl;
2727

28+
private boolean isLiked;
29+
2830
private Double calcium;
2931
private Double carbohydrate;
3032
private Double cholesterol;

src/main/java/com/DecodEat/domain/products/dto/response/ProductResponseDTO.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public static class ProductListItemDTO {
5555

5656
@Schema(description = "뷴석 상태", example = "COMPLETED")
5757
private DecodeStatus decodeStatus;
58+
59+
@Schema(description = "좋아요 여부", example = "true")
60+
private boolean isLiked;
5861
}
5962

6063
}

src/main/java/com/DecodEat/domain/products/repository/ProductLikeRepository.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,23 @@
44
import com.DecodEat.domain.products.entity.ProductLike;
55
import com.DecodEat.domain.users.entity.User;
66
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Query;
8+
import org.springframework.data.repository.query.Param;
79

10+
import java.util.List;
811
import java.util.Optional;
912

1013
public interface ProductLikeRepository extends JpaRepository<ProductLike, Long> {
1114

1215
Optional<ProductLike> findByUserAndProduct(User user, Product product);
1316

17+
// 여러 제품에 대한 좋아요 여부 조회 ( 제품 리스트 조회시 N + 1 문제 해결)
18+
@Query("SELECT pl.product.productId " +
19+
"FROM ProductLike pl " +
20+
"WHERE pl.user = :user AND pl.product.productId IN :productIds")
21+
List<Long> findLikedProductIdsByUserAndProductIds(@Param("user") User user,
22+
@Param("productIds") List<Long> productIds);
23+
24+
boolean existsByUserAndProduct(User user, Product product);
25+
1426
}

src/main/java/com/DecodEat/domain/products/service/ProductService.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@
2727
import org.springframework.web.multipart.MultipartFile;
2828

2929
import javax.swing.*;
30-
import java.util.ArrayList;
31-
import java.util.List;
32-
import java.util.Optional;
33-
import java.util.UUID;
30+
import java.util.*;
3431
import java.util.stream.Collectors;
3532

3633
import static com.DecodEat.global.apiPayload.code.status.ErrorStatus.*;
@@ -62,7 +59,12 @@ public ProductDetailDto getDetail(Long id, User user) {
6259

6360
ProductNutrition productNutrition = productNutritionRepository.findByProduct(product).orElseThrow(() -> new GeneralException(PRODUCT_NUTRITION_NOT_EXISTED));
6461

65-
return ProductConverter.toProductDetailDto(product, imageUrls, productNutrition);
62+
// 좋아요 여부 확인
63+
boolean isLiked = false;
64+
if(user != null){
65+
isLiked = productLikeRepository.existsByUserAndProduct(user,product);
66+
}
67+
return ProductConverter.toProductDetailDto(product, imageUrls, productNutrition, isLiked);
6668
}
6769

6870
public ProductRegisterResponseDto addProduct(User user, ProductRegisterRequestDto requestDto, MultipartFile productImage, List<MultipartFile> productInfoImages) {
@@ -110,11 +112,25 @@ public ProductRegisterResponseDto addProduct(User user, ProductRegisterRequestDt
110112
}
111113

112114
@Transactional(readOnly = true)
113-
public ProductResponseDTO.ProductListResultDTO getProducts(Long cursorId) {
115+
public ProductResponseDTO.ProductListResultDTO getProducts(Long cursorId, User user) {
114116
Pageable pageable = PageRequest.of(0, PAGE_SIZE);
115117
Slice<Product> slice = productRepository.findCompletedProductsByCursor(cursorId, pageable);
116118

117-
return ProductConverter.toProductListResultDTO(slice);
119+
// 기본값: 전부 false
120+
Set<Long> likedProductIds = Collections.emptySet();
121+
122+
// user가 null이 아닐 때만 DB에서 좋아요 여부 조회
123+
if (user != null && !slice.isEmpty()) {
124+
List<Long> productIds = slice.getContent().stream()
125+
.map(Product::getProductId)
126+
.toList();
127+
128+
likedProductIds = new HashSet<>(
129+
productLikeRepository.findLikedProductIdsByUserAndProductIds(user, productIds)
130+
);
131+
}
132+
133+
return ProductConverter.toProductListResultDTO(slice, likedProductIds);
118134
}
119135

120136
// todo: 검색은 상품 엔티티와 1:1 매핑 불가능 -> userbehavior 어떻게?

0 commit comments

Comments
 (0)