Skip to content

Commit 8ed7f37

Browse files
authored
Merge pull request #52 from Decodeat/Feat/#46
Feat: 제품 좋아요/취소 API 구현
2 parents b07f0c2 + 1fa5e41 commit 8ed7f37

File tree

6 files changed

+126
-13
lines changed

6 files changed

+126
-13
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,12 @@ public ApiResponse<PageResponseDto<ProductRegisterHistoryDto>> getRegisterHistor
105105
return ApiResponse.onSuccess(productService.getRegisterHistory(user, pageable));
106106
}
107107

108+
@Operation(summary = "제품 좋아요 추가/취소", description = "좋아요를 누르면 추가, 다시 누르면 취소됩니다.")
109+
@PostMapping("/{productId}/like")
110+
public ApiResponse<ProductLikeResponseDTO> addOrUpdateLike(
111+
@CurrentUser User user,
112+
@Parameter(description = "제품 ID") @PathVariable Long productId
113+
) {
114+
return ApiResponse.onSuccess(productService.addOrUpdateLike(user.getId(), productId));
115+
}
108116
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.DecodEat.domain.products.dto.request.ProductRegisterRequestDto;
44
import com.DecodEat.domain.products.dto.response.*;
55
import com.DecodEat.domain.products.entity.Product;
6+
import com.DecodEat.domain.products.entity.ProductInfoImage;
67
import com.DecodEat.domain.products.entity.ProductNutrition;
78
import com.DecodEat.domain.products.entity.RawMaterial.RawMaterialCategory;
89
import org.springframework.data.domain.Slice;
@@ -121,4 +122,13 @@ public static ProductResponseDTO.ProductListResultDTO toProductListResultDTO(Sli
121122
.nextCursorId(nextCursorId)
122123
.build();
123124
}
125+
126+
public static ProductLikeResponseDTO toProductLikeDTO(Long productId, boolean isLiked) {
127+
128+
return ProductLikeResponseDTO.builder()
129+
.productId(productId)
130+
.isLiked(isLiked)
131+
.build();
132+
133+
}
124134
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.DecodEat.domain.products.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@Builder
11+
@AllArgsConstructor
12+
@NoArgsConstructor
13+
@Schema(description = "제품 좋아요 응답 정보")
14+
public class ProductLikeResponseDTO {
15+
16+
@Schema(description = "제품 ID", example = "1")
17+
private Long productId;
18+
19+
@Schema(description = "좋아요 여부", example = "true")
20+
private boolean isLiked;
21+
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.DecodEat.domain.products.entity;
2+
3+
4+
import com.DecodEat.domain.users.entity.User;
5+
import com.DecodEat.global.common.BaseEntity;
6+
import jakarta.persistence.*;
7+
import lombok.*;
8+
9+
@Entity
10+
@Getter
11+
@AllArgsConstructor
12+
@NoArgsConstructor
13+
@Builder
14+
@Table(
15+
name = "product_like",
16+
uniqueConstraints = {
17+
@UniqueConstraint(columnNames = {"user_id", "product_id"})
18+
}
19+
)// 한 유저는 하나의 제품에 대해 한번의 좋아요만 하도록 유니크 제약조건 설정
20+
public class ProductLike extends BaseEntity {
21+
22+
@Id
23+
@GeneratedValue(strategy = GenerationType.IDENTITY)
24+
private Long id;
25+
26+
@ManyToOne(fetch = FetchType.LAZY)
27+
@JoinColumn(name = "user_id", nullable = false)
28+
private User user;
29+
30+
@ManyToOne(fetch = FetchType.LAZY)
31+
@JoinColumn(name = "product_id", nullable = false)
32+
private Product product;
33+
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.DecodEat.domain.products.repository;
2+
3+
import com.DecodEat.domain.products.entity.Product;
4+
import com.DecodEat.domain.products.entity.ProductLike;
5+
import com.DecodEat.domain.users.entity.User;
6+
import org.springframework.data.jpa.repository.JpaRepository;
7+
8+
import java.util.Optional;
9+
10+
public interface ProductLikeRepository extends JpaRepository<ProductLike, Long> {
11+
12+
Optional<ProductLike> findByUserAndProduct(User user, Product product);
13+
14+
}

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

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,13 @@
55
import com.DecodEat.domain.products.dto.request.AnalysisRequestDto;
66
import com.DecodEat.domain.products.dto.request.ProductRegisterRequestDto;
77
import com.DecodEat.domain.products.dto.response.*;
8-
import com.DecodEat.domain.products.entity.DecodeStatus;
9-
import com.DecodEat.domain.products.entity.Product;
10-
import com.DecodEat.domain.products.entity.ProductInfoImage;
11-
import com.DecodEat.domain.products.entity.ProductNutrition;
12-
import com.DecodEat.domain.products.entity.ProductRawMaterial;
8+
import com.DecodEat.domain.products.entity.*;
139
import com.DecodEat.domain.products.entity.RawMaterial.RawMaterial;
1410
import com.DecodEat.domain.products.entity.RawMaterial.RawMaterialCategory;
15-
import com.DecodEat.domain.products.repository.ProductImageRepository;
16-
import com.DecodEat.domain.products.repository.ProductNutritionRepository;
17-
import com.DecodEat.domain.products.repository.ProductRawMaterialRepository;
18-
import com.DecodEat.domain.products.repository.ProductRepository;
19-
import com.DecodEat.domain.products.repository.RawMaterialRepository;
20-
import com.DecodEat.domain.products.repository.ProductSpecification;
11+
import com.DecodEat.domain.products.repository.*;
2112
import com.DecodEat.domain.users.entity.Behavior;
2213
import com.DecodEat.domain.users.entity.User;
14+
import com.DecodEat.domain.users.repository.UserRepository;
2315
import com.DecodEat.domain.users.service.UserBehaviorService;
2416
import com.DecodEat.global.aws.s3.AmazonS3Manager;
2517
import com.DecodEat.global.dto.PageResponseDto;
@@ -37,6 +29,7 @@
3729
import javax.swing.*;
3830
import java.util.ArrayList;
3931
import java.util.List;
32+
import java.util.Optional;
4033
import java.util.UUID;
4134
import java.util.stream.Collectors;
4235

@@ -55,9 +48,9 @@ public class ProductService {
5548
private final AmazonS3Manager amazonS3Manager;
5649
private final PythonAnalysisClient pythonAnalysisClient;
5750
private final UserBehaviorService userBehaviorService;
58-
59-
6051
private static final int PAGE_SIZE = 12;
52+
private final UserRepository userRepository;
53+
private final ProductLikeRepository productLikeRepository;
6154

6255
public ProductDetailDto getDetail(Long id, User user) {
6356
Product product = productRepository.findById(id).orElseThrow(() -> new GeneralException(PRODUCT_NOT_EXISTED));
@@ -325,4 +318,36 @@ private Double parseDouble(String value) {
325318
return null;
326319
}
327320
}
321+
322+
@Transactional
323+
public ProductLikeResponseDTO addOrUpdateLike(Long userId, Long productId) {
324+
325+
// 1. 유저 확인
326+
User user = userRepository.findById(userId)
327+
.orElseThrow(() -> new GeneralException(USER_NOT_EXISTED));
328+
329+
// 2. 제품 확인
330+
Product product = productRepository.findById(productId)
331+
.orElseThrow(() -> new GeneralException(PRODUCT_NOT_EXISTED));
332+
333+
// 3. 기존 좋아요 여부 확인
334+
Optional<ProductLike> existingLike = productLikeRepository.findByUserAndProduct(user, product);
335+
336+
boolean isLiked;
337+
338+
if (existingLike.isPresent()) {
339+
// 이미 눌렀으면 → 좋아요 취소
340+
productLikeRepository.delete(existingLike.get());
341+
isLiked = false;
342+
} else {
343+
// 처음 누르면 → 좋아요 추가
344+
ProductLike productLike = ProductLike.builder()
345+
.user(user)
346+
.product(product)
347+
.build();
348+
productLikeRepository.save(productLike);
349+
isLiked = true;
350+
}
351+
return ProductConverter.toProductLikeDTO(productId, isLiked);
352+
}
328353
}

0 commit comments

Comments
 (0)