From d7fa34b8571a7334cac17eeb72f6acbeea69d092 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Tue, 13 Aug 2024 12:55:48 +0900 Subject: [PATCH 1/6] SCRUM-44 feat: product folder init --- .../golagola/domain/product/controller/ProductController.java | 4 ++++ .../kakaoteck/golagola/domain/product/dto/ProductRequest.java | 4 ++++ .../golagola/domain/product/dto/ProductResponse.java | 4 ++++ .../golagola/domain/product/service/ProductService.java | 4 ++++ 4 files changed, 16 insertions(+) create mode 100644 src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java create mode 100644 src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java create mode 100644 src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java create mode 100644 src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java new file mode 100644 index 0000000..503e99f --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.domain.product.controller; + +public class ProductController { +} diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java new file mode 100644 index 0000000..e9370f9 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.domain.product.dto; + +public record ProductRequest() { +} diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java new file mode 100644 index 0000000..a697e98 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.domain.product.dto; + +public record ProductResponse() { +} diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java new file mode 100644 index 0000000..eb842d5 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.domain.product.service; + +public class ProductService { +} From b7b58b0a958037b89098b00acf37d0e0afa4c73b Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Tue, 13 Aug 2024 13:11:19 +0900 Subject: [PATCH 2/6] =?UTF-8?q?SCRUM-44=20feat:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 25 +++++++++++ .../domain/product/dto/ProductRequest.java | 17 ++++++- .../domain/product/entity/Product.java | 4 +- .../product/service/ProductService.java | 44 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java index 503e99f..7f25933 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java @@ -1,4 +1,29 @@ package com.kakaoteck.golagola.domain.product.controller; +import com.kakaoteck.golagola.domain.product.dto.ProductRequest; +import com.kakaoteck.golagola.domain.product.service.ProductService; +import com.kakaoteck.golagola.domain.seller.dto.SellerResponse; +import com.kakaoteck.golagola.domain.seller.entity.Seller; +import com.kakaoteck.golagola.global.common.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/product") +@CrossOrigin("*") public class ProductController { + + private final ProductService productService; + + @Operation(summary = "상품 등록", description = "상품을 등록합니다. 판매자 전용") + @PostMapping() + public ApiResponse postProduct( + @AuthenticationPrincipal Seller seller, + @RequestBody ProductRequest request + ) { + return ApiResponse.onSuccess(productService.postProduct(seller, request)); + } } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java index e9370f9..7fb83b3 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductRequest.java @@ -1,4 +1,19 @@ package com.kakaoteck.golagola.domain.product.dto; -public record ProductRequest() { +import com.kakaoteck.golagola.global.common.enums.Category; +import com.kakaoteck.golagola.global.common.enums.DetailCategory; +import lombok.Builder; + +@Builder +public record ProductRequest( + String productName, + String productExplanation, + String productImage, + Long productPrice, + Long productInventory, + Category category, + DetailCategory detailCategory, + Long discount, + Long productQuantity +) { } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java b/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java index 3031f8f..0ec8649 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java @@ -77,10 +77,10 @@ public class Product extends BaseEntity { @Column(nullable = false) private Long productQuantity; - @Column(nullable = false) + @Column() private Float predictReviewStar; - @Column(nullable = false) + @Column() private Float productStar; public static Product from(Long productId, Seller seller, Cart cart, List reviewList, diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java index eb842d5..de30e34 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java @@ -1,4 +1,48 @@ package com.kakaoteck.golagola.domain.product.service; +import com.kakaoteck.golagola.domain.product.dto.ProductRequest; +import com.kakaoteck.golagola.domain.product.entity.Product; +import com.kakaoteck.golagola.domain.product.repository.ProductRepository; +import com.kakaoteck.golagola.domain.seller.entity.Seller; +import com.kakaoteck.golagola.domain.seller.repository.SellerRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalTime; + +@Service +@RequiredArgsConstructor +@Transactional +@Slf4j public class ProductService { + + private final SellerRepository sellerRepository; + private final ProductRepository productRepository; + + public String postProduct(Seller seller, ProductRequest request) { + // Product 객체 생성 + Product product = Product.builder() + .seller(seller) + .productName(request.productName()) + .productExplanation(request.productExplanation()) + .productImage(request.productImage()) + .productPrice(request.productPrice()) + .productInventory(request.productInventory()) + .category(request.category()) + .detailCategory(request.detailCategory()) + .discount(request.discount()) + .createTime(LocalTime.now()) + .updateTime(LocalTime.now()) + .productQuantity(request.productQuantity()) + .predictReviewStar(0.0f) // 초기 예상 리뷰 별점 + .productStar(0.0f) // 초기 실제 리뷰 별점 + .build(); + + // Product 저장 + productRepository.save(product); + + return "상품등록 성공"; + } } From 79a1b53100aee0fc16500f1fd2f863a06d9fca25 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Tue, 13 Aug 2024 13:22:55 +0900 Subject: [PATCH 3/6] =?UTF-8?q?SCRUM-44=20feat:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 11 +++++ .../domain/product/dto/ProductResponse.java | 20 ++++++++- .../domain/product/entity/Product.java | 16 +++++++ .../product/service/ProductService.java | 44 +++++++++++++++++++ .../common/code/status/ErrorStatus.java | 1 + 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java index 7f25933..d97e4a8 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java @@ -1,6 +1,7 @@ package com.kakaoteck.golagola.domain.product.controller; import com.kakaoteck.golagola.domain.product.dto.ProductRequest; +import com.kakaoteck.golagola.domain.product.dto.ProductResponse; import com.kakaoteck.golagola.domain.product.service.ProductService; import com.kakaoteck.golagola.domain.seller.dto.SellerResponse; import com.kakaoteck.golagola.domain.seller.entity.Seller; @@ -26,4 +27,14 @@ public ApiResponse postProduct( ) { return ApiResponse.onSuccess(productService.postProduct(seller, request)); } + + @Operation(summary = "상품 수정", description = "상품 정보를 수정합니다.") + @PutMapping() + public ApiResponse modifyProduct( + @AuthenticationPrincipal Seller seller, + @PathVariable Long productId, + @RequestBody ProductRequest request + ) { + return ApiResponse.onSuccess(productService.modifyProduct(seller, productId, request)); + } } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java index a697e98..078c281 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java @@ -1,4 +1,22 @@ package com.kakaoteck.golagola.domain.product.dto; -public record ProductResponse() { +import com.kakaoteck.golagola.global.common.enums.Category; +import com.kakaoteck.golagola.global.common.enums.DetailCategory; +import lombok.Builder; + +@Builder +public record ProductResponse( + Long productId, + String productName, + String productExplanation, + String productImage, + Long productPrice, + Long productInventory, + Category category, + DetailCategory detailCategory, + Long discount, + Long productQuantity, + Float predictReviewStar, + Float productStar +) { } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java b/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java index 0ec8649..ddef5af 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java @@ -83,6 +83,22 @@ public class Product extends BaseEntity { @Column() private Float productStar; + + public void updateProduct(String productName, String productExplanation, String productImage, Long productPrice, + Long productInventory, Category category, DetailCategory detailCategory, + Long discount, Long productQuantity, LocalTime updateTime) { + this.productName = productName; + this.productExplanation = productExplanation; + this.productImage = productImage; + this.productPrice = productPrice; + this.productInventory = productInventory; + this.category = category; + this.detailCategory = detailCategory; + this.discount = discount; + this.productQuantity = productQuantity; + this.updateTime = updateTime; + } + public static Product from(Long productId, Seller seller, Cart cart, List reviewList, List orderProductList, String productName, String productExplanation, String productImage, Long productPrice, diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java index de30e34..7ae6b82 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java @@ -1,10 +1,13 @@ package com.kakaoteck.golagola.domain.product.service; import com.kakaoteck.golagola.domain.product.dto.ProductRequest; +import com.kakaoteck.golagola.domain.product.dto.ProductResponse; import com.kakaoteck.golagola.domain.product.entity.Product; import com.kakaoteck.golagola.domain.product.repository.ProductRepository; import com.kakaoteck.golagola.domain.seller.entity.Seller; import com.kakaoteck.golagola.domain.seller.repository.SellerRepository; +import com.kakaoteck.golagola.global.common.code.status.ErrorStatus; +import com.kakaoteck.golagola.global.common.exception.GeneralException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -45,4 +48,45 @@ public String postProduct(Seller seller, ProductRequest request) { return "상품등록 성공"; } + + public ProductResponse modifyProduct(Seller seller, Long productId, ProductRequest request) { + // productId로 해당 상품을 찾음 + Product product = productRepository.findById(productId) + .orElseThrow(() -> new GeneralException(ErrorStatus._NOT_FOUND_PRODUCT)); + + // 해당 상품이 현재 로그인한 seller가 등록한 것인지 확인 + if (!product.getSeller().getSellerId().equals(seller.getSellerId())) { + throw new GeneralException(ErrorStatus._UNAUTHORIZED_ACCESS); + } + + // Product 정보 업데이트 + product.updateProduct( + request.productName(), + request.productExplanation(), + request.productImage(), + request.productPrice(), + request.productInventory(), + request.category(), + request.detailCategory(), + request.discount(), + request.productQuantity(), + LocalTime.now() // updateTime을 현재 시간으로 업데이트 + ); + + // 업데이트된 Product 저장 + productRepository.save(product); + + return ProductResponse.builder() + .productId(product.getProductId()) + .productName(product.getProductName()) + .productExplanation(product.getProductExplanation()) + .productImage(product.getProductImage()) + .productPrice(product.getProductPrice()) + .productInventory(product.getProductInventory()) + .category(product.getCategory()) + .detailCategory(product.getDetailCategory()) + .discount(product.getDiscount()) + .productQuantity(product.getProductQuantity()) + .build(); + } } diff --git a/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java b/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java index 98715c8..f7e5f86 100644 --- a/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java +++ b/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java @@ -19,6 +19,7 @@ public enum ErrorStatus implements BaseErrorCode { _NOT_FOUND_USER(HttpStatus.NOT_FOUND, "USER400", "사용자가 존재하지 않습니다."), _LOGIN_USER_INVALID(HttpStatus.BAD_REQUEST, "USER401", "로그인 중 오류가 발생하였습니다."), _INVALID_USER(HttpStatus.BAD_REQUEST, "USER401" , "아이디 또는 비밀번호가 틀렸습니다."), + _UNAUTHORIZED_ACCESS(HttpStatus.UNAUTHORIZED, "USER401", "본인이 등록한 제품만 수정할 수 있습니다."), // Product 에러 _NOT_FOUND_PRODUCT(HttpStatus.NOT_FOUND, "USER400", "제품이 존재하지 않습니다."), From 5534d2a42eb08623afde6ba0bbaa88e51e9655ca Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Tue, 13 Aug 2024 13:30:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?SCRUM-44=20feat:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.java | 13 ++++++++++++- .../domain/product/service/ProductService.java | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java index d97e4a8..2fe7a71 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/controller/ProductController.java @@ -29,7 +29,7 @@ public ApiResponse postProduct( } @Operation(summary = "상품 수정", description = "상품 정보를 수정합니다.") - @PutMapping() + @PutMapping("/{productId}") public ApiResponse modifyProduct( @AuthenticationPrincipal Seller seller, @PathVariable Long productId, @@ -37,4 +37,15 @@ public ApiResponse modifyProduct( ) { return ApiResponse.onSuccess(productService.modifyProduct(seller, productId, request)); } + + @Operation(summary = "상품 삭제", description = "상품을 삭제합니다. 판매자 전용") + @DeleteMapping("/{productId}") + public ApiResponse deleteProduct( + @AuthenticationPrincipal Seller seller, + @PathVariable Long productId + ) { + productService.deleteProduct(seller, productId); + return ApiResponse.onSuccess("상품이 성공적으로 삭제되었습니다."); + } + } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java index 7ae6b82..490308c 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java @@ -89,4 +89,18 @@ public ProductResponse modifyProduct(Seller seller, Long productId, ProductReque .productQuantity(product.getProductQuantity()) .build(); } + + public void deleteProduct(Seller seller, Long productId) { + // productId로 Product를 찾음 + Product product = productRepository.findById(productId) + .orElseThrow(() -> new GeneralException(ErrorStatus._NOT_FOUND_PRODUCT)); + + // 해당 Product가 현재 로그인된 seller가 등록한 제품인지 확인 + if (!product.getSeller().getSellerId().equals(seller.getSellerId())) { + throw new GeneralException(ErrorStatus._UNAUTHORIZED_ACCESS); + } + + // Product 삭제 + productRepository.delete(product); + } } From cf500f254aef16d3eb99a54445c5476d469c0d3d Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Tue, 13 Aug 2024 15:04:07 +0900 Subject: [PATCH 5/6] =?UTF-8?q?SCRUM-44=20fix:=20=EC=A7=80=EC=97=B0?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EB=AC=B8=EC=A0=9C,=20=EB=8B=A4=EB=8C=80?= =?UTF-8?q?=EB=8B=A4=20=EA=B4=80=EA=B3=84,=20dto=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../golagola/domain/buyer/entity/Buyer.java | 30 ++++--- .../domain/cart/dto/CartResponse.java | 4 +- .../golagola/domain/cart/entity/Cart.java | 15 +++- .../domain/cart/entity/CartProduct.java | 43 ++++++++++ .../repository/CartProductRepository.java | 10 +++ .../domain/cart/service/CartService.java | 80 ++++++++++++------- .../domain/product/dto/ProductResponse.java | 7 ++ .../domain/product/entity/Product.java | 35 ++++---- .../golagola/domain/seller/entity/Seller.java | 2 + .../common/code/status/ErrorStatus.java | 1 + 10 files changed, 161 insertions(+), 66 deletions(-) create mode 100644 src/main/java/com/kakaoteck/golagola/domain/cart/entity/CartProduct.java create mode 100644 src/main/java/com/kakaoteck/golagola/domain/cart/repository/CartProductRepository.java diff --git a/src/main/java/com/kakaoteck/golagola/domain/buyer/entity/Buyer.java b/src/main/java/com/kakaoteck/golagola/domain/buyer/entity/Buyer.java index a7fac3a..ec1386f 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/buyer/entity/Buyer.java +++ b/src/main/java/com/kakaoteck/golagola/domain/buyer/entity/Buyer.java @@ -2,6 +2,7 @@ import com.kakaoteck.golagola.domain.buyer.dto.BuyerRequest; import com.kakaoteck.golagola.domain.cart.entity.Cart; +import com.kakaoteck.golagola.domain.cart.entity.CartProduct; import com.kakaoteck.golagola.domain.order.entity.Order; import com.kakaoteck.golagola.domain.product.entity.Product; import com.kakaoteck.golagola.domain.review.entity.Review; @@ -25,8 +26,8 @@ @Entity @NoArgsConstructor @AllArgsConstructor -@Builder @Getter +@Builder @Table(name = "buyer_table") public class Buyer extends BaseEntity implements UserDetails { @@ -63,7 +64,7 @@ public class Buyer extends BaseEntity implements UserDetails { @Column(nullable = false) private LocalDate registerDate; - @OneToOne(mappedBy = "buyer", cascade = CascadeType.ALL) + @OneToOne(mappedBy = "buyer", cascade = CascadeType.ALL, orphanRemoval = true) private Cart cart; @OneToMany(mappedBy = "buyer", cascade = CascadeType.ALL) @@ -114,28 +115,36 @@ public void updateProfile(BuyerRequest.MyPagePutDto request) { } public void assignCart(Cart cart) { - if (cart != null) { - this.cart = cart; + this.cart = cart; + if (cart.getBuyer() != this) { cart.assignBuyer(this); } } - public void addProductToCart(Product product) { + // Cart를 자동으로 초기화 + @PrePersist + public void initializeCart() { if (this.cart == null) { - this.cart = new Cart(); // 새로운 Cart 객체 생성 - this.cart.assignBuyer(this); // Cart와 Buyer 간의 양방향 연관관계 설정 + Cart newCart = new Cart(); + newCart.assignBuyer(this); + this.cart = newCart; } - this.cart.getProductList().add(product); } public Cart getOrCreateCart() { if (this.cart == null) { - this.cart = new Cart(); // 새로운 Cart 객체 생성 - this.cart.assignBuyer(this); // Cart와 Buyer 간의 양방향 연관관계 설정 + Cart newCart = new Cart(); + newCart.assignBuyer(this); + this.cart = newCart; } return this.cart; } + public void addProductToCart(Product product) { + Cart cart = this.getOrCreateCart(); + cart.addProduct(product); + } + public static Buyer from(Long buyerId, String nickname, String realName, Gender gender, String email, String password, String address, String phoneNum, Role role, LocalDate registerDate) { return Buyer.builder() @@ -152,4 +161,3 @@ public static Buyer from(Long buyerId, String nickname, String realName, Gender .build(); } } - diff --git a/src/main/java/com/kakaoteck/golagola/domain/cart/dto/CartResponse.java b/src/main/java/com/kakaoteck/golagola/domain/cart/dto/CartResponse.java index 2372ce0..9ceb9b6 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/cart/dto/CartResponse.java +++ b/src/main/java/com/kakaoteck/golagola/domain/cart/dto/CartResponse.java @@ -1,5 +1,6 @@ package com.kakaoteck.golagola.domain.cart.dto; +import com.kakaoteck.golagola.domain.product.dto.ProductResponse; import com.kakaoteck.golagola.domain.product.entity.Product; import lombok.Builder; @@ -7,7 +8,6 @@ @Builder public record CartResponse( - List productList + List productList ) { - } diff --git a/src/main/java/com/kakaoteck/golagola/domain/cart/entity/Cart.java b/src/main/java/com/kakaoteck/golagola/domain/cart/entity/Cart.java index ad35481..c1b4ca6 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/cart/entity/Cart.java +++ b/src/main/java/com/kakaoteck/golagola/domain/cart/entity/Cart.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; import java.util.List; @Entity @@ -22,18 +23,24 @@ public class Cart { @JoinColumn(name = "buyer_id") private Buyer buyer; - @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL) - private List productList; + @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) + private List cartProducts = new ArrayList<>(); @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long cartId; public void assignBuyer(Buyer buyer) { - if (buyer != null) { - this.buyer = buyer; + this.buyer = buyer; + if (buyer.getCart() != this) { buyer.assignCart(this); } } + public void addProduct(Product product) { + CartProduct cartProduct = new CartProduct(); + cartProduct.assignCart(this); + cartProduct.assignProduct(product); + this.cartProducts.add(cartProduct); + } } diff --git a/src/main/java/com/kakaoteck/golagola/domain/cart/entity/CartProduct.java b/src/main/java/com/kakaoteck/golagola/domain/cart/entity/CartProduct.java new file mode 100644 index 0000000..b6257a7 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/domain/cart/entity/CartProduct.java @@ -0,0 +1,43 @@ +package com.kakaoteck.golagola.domain.cart.entity; + +import com.kakaoteck.golagola.domain.product.entity.Product; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Builder +@Table(name = "cart_product_table") +public class CartProduct { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "cart_id") + private Cart cart; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "product_id") + private Product product; + + public void assignCart(Cart cart) { + this.cart = cart; + if (!cart.getCartProducts().contains(this)) { + cart.getCartProducts().add(this); + } + } + + public void assignProduct(Product product) { + this.product = product; + if (!product.getCartProducts().contains(this)) { + product.getCartProducts().add(this); + } + } +} diff --git a/src/main/java/com/kakaoteck/golagola/domain/cart/repository/CartProductRepository.java b/src/main/java/com/kakaoteck/golagola/domain/cart/repository/CartProductRepository.java new file mode 100644 index 0000000..155a72e --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/domain/cart/repository/CartProductRepository.java @@ -0,0 +1,10 @@ +package com.kakaoteck.golagola.domain.cart.repository; + +import com.kakaoteck.golagola.domain.cart.entity.Cart; +import com.kakaoteck.golagola.domain.cart.entity.CartProduct; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CartProductRepository extends JpaRepository { +} diff --git a/src/main/java/com/kakaoteck/golagola/domain/cart/service/CartService.java b/src/main/java/com/kakaoteck/golagola/domain/cart/service/CartService.java index 238c57c..31cac9e 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/cart/service/CartService.java +++ b/src/main/java/com/kakaoteck/golagola/domain/cart/service/CartService.java @@ -1,21 +1,23 @@ package com.kakaoteck.golagola.domain.cart.service; -import com.kakaoteck.golagola.domain.buyer.dto.BuyerResponse; import com.kakaoteck.golagola.domain.buyer.entity.Buyer; -import com.kakaoteck.golagola.domain.cart.dto.CartRequest; import com.kakaoteck.golagola.domain.cart.dto.CartResponse; +import com.kakaoteck.golagola.domain.cart.entity.Cart; +import com.kakaoteck.golagola.domain.cart.entity.CartProduct; +import com.kakaoteck.golagola.domain.cart.repository.CartProductRepository; +import com.kakaoteck.golagola.domain.product.dto.ProductResponse; import com.kakaoteck.golagola.domain.product.entity.Product; import com.kakaoteck.golagola.domain.product.repository.ProductRepository; import com.kakaoteck.golagola.global.common.code.status.ErrorStatus; import com.kakaoteck.golagola.global.common.exception.GeneralException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.hibernate.Hibernate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; import java.util.List; -import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -24,16 +26,29 @@ public class CartService { private final ProductRepository productRepository; + private final CartProductRepository cartProductRepository; public CartResponse getCartProducts(Buyer buyer) { - // Cart가 null이 아닌 경우에만 productList를 가져오고, 그렇지 않으면 빈 리스트를 반환 - List productList = Collections.emptyList(); - if (buyer.getCart() != null) { - productList = buyer.getCart().getProductList() != null ? buyer.getCart().getProductList() : Collections.emptyList(); - } + Cart cart = buyer.getCart(); + + // Lazy 로딩을 명시적으로 초기화 + Hibernate.initialize(cart.getCartProducts()); + + // CartProduct 객체에서 Product를 추출하여 리스트 생성 + List products = cart.getCartProducts().stream() + .map(cartProduct -> { + Product product = cartProduct.getProduct(); + return ProductResponse.ProductDto.builder() + .productId(product.getProductId()) + .productName(product.getProductName()) + .productExplanation(product.getProductExplanation()) + .productPrice(product.getProductPrice()) + .build(); + }) + .collect(Collectors.toList()); return CartResponse.builder() - .productList(productList) + .productList(products) .build(); } @@ -41,41 +56,48 @@ public CartResponse addCartProduct(Buyer buyer, Long productId) { Product product = productRepository.findById(productId) .orElseThrow(() -> new GeneralException(ErrorStatus._NOT_FOUND_PRODUCT)); - // Buyer의 cart에 product를 추가 - buyer.addProductToCart(product); + Cart cart = buyer.getOrCreateCart(); - return CartResponse.builder() - .productList(buyer.getCart().getProductList()) + // 장바구니에 이미 동일한 상품이 있는지 확인 + boolean productExistsInCart = cart.getCartProducts().stream() + .anyMatch(cp -> cp.getProduct().getProductId().equals(productId)); + + if (productExistsInCart) { + throw new GeneralException(ErrorStatus._PRODUCT_ALREADY_IN_CART); // 이미 존재한다면 예외를 던집니다. + } + + // 장바구니에 상품 추가 + CartProduct cartProduct = CartProduct.builder() + .cart(cart) + .product(product) .build(); + + cartProductRepository.save(cartProduct); + + return getCartProducts(buyer); } public CartResponse deleteCartProduct(Buyer buyer, Long productId) { - if (buyer.getCart() == null || buyer.getCart().getProductList() == null) { - throw new GeneralException(ErrorStatus._NOT_FOUND_PRODUCT_IN_CART); - } + Cart cart = buyer.getCart(); - // Buyer의 cart에서 해당 productId에 해당하는 제품을 찾음 - List productList = buyer.getCart().getProductList(); - Product productToRemove = productList.stream() - .filter(product -> product.getProductId().equals(productId)) + CartProduct cartProduct = cart.getCartProducts().stream() + .filter(cp -> cp.getProduct().getProductId().equals(productId)) .findFirst() .orElseThrow(() -> new GeneralException(ErrorStatus._NOT_FOUND_PRODUCT_IN_CART)); - // 제품을 장바구니에서 삭제 - productList.remove(productToRemove); + cartProductRepository.delete(cartProduct); - return CartResponse.builder() - .productList(productList) - .build(); + return getCartProducts(buyer); } public String emptyCart(Buyer buyer) { - if (buyer.getCart() == null || buyer.getCart().getProductList() == null) { + Cart cart = buyer.getCart(); + + if (cart.getCartProducts().isEmpty()) { throw new GeneralException(ErrorStatus._CART_IS_ALREADY_EMPTY); } - // 장바구니를 비움 - buyer.getCart().getProductList().clear(); + cartProductRepository.deleteAll(cart.getCartProducts()); return "장바구니에 있는 모든 제품이 삭제되었습니다."; } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java index 078c281..9b5eec4 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/dto/ProductResponse.java @@ -19,4 +19,11 @@ public record ProductResponse( Float predictReviewStar, Float productStar ) { + @Builder + public record ProductDto (Long productId, + String productName, + String productExplanation, + Long productPrice) { + + } } diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java b/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java index ddef5af..52db5a0 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/entity/Product.java @@ -1,6 +1,7 @@ package com.kakaoteck.golagola.domain.product.entity; -import com.kakaoteck.golagola.domain.cart.entity.Cart; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.kakaoteck.golagola.domain.cart.entity.CartProduct; import com.kakaoteck.golagola.domain.orderProduct.entity.OrderProduct; import com.kakaoteck.golagola.domain.review.entity.Review; import com.kakaoteck.golagola.domain.seller.entity.Seller; @@ -30,11 +31,11 @@ public class Product extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "seller_id") + @JsonBackReference private Seller seller; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "cart_id") - private Cart cart; + @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) + private List cartProducts; @OneToMany(mappedBy = "product", cascade = CascadeType.ALL) private List reviewList; @@ -83,7 +84,7 @@ public class Product extends BaseEntity { @Column() private Float productStar; - + // Product 수정 메서드 public void updateProduct(String productName, String productExplanation, String productImage, Long productPrice, Long productInventory, Category category, DetailCategory detailCategory, Long discount, Long productQuantity, LocalTime updateTime) { @@ -99,18 +100,13 @@ public void updateProduct(String productName, String productExplanation, String this.updateTime = updateTime; } - public static Product from(Long productId, Seller seller, Cart cart, List reviewList, - List orderProductList, String productName, - String productExplanation, String productImage, Long productPrice, - Long productInventory, Category category, DetailCategory detailCategory, - Long discount, LocalTime createTime, LocalTime updateTime, - Long productQuantity, Float predictReviewStar, Float productStar) { + // Product 생성 메서드 + public static Product createProduct(Seller seller, String productName, String productExplanation, + String productImage, Long productPrice, Long productInventory, + Category category, DetailCategory detailCategory, Long discount, + Long productQuantity) { return Product.builder() - .productId(productId) .seller(seller) - .cart(cart) - .reviewList(reviewList) - .orderProductList(orderProductList) .productName(productName) .productExplanation(productExplanation) .productImage(productImage) @@ -119,12 +115,11 @@ public static Product from(Long productId, Seller seller, Cart cart, List productList = new ArrayList<>(); @OneToMany(mappedBy = "seller", cascade = CascadeType.ALL) diff --git a/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java b/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java index f7e5f86..c40e425 100644 --- a/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java +++ b/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java @@ -27,6 +27,7 @@ public enum ErrorStatus implements BaseErrorCode { // Cart 에러 _CART_IS_ALREADY_EMPTY(HttpStatus.BAD_REQUEST, "USER400", "장바구니가 이미 비어있습니다."), + _PRODUCT_ALREADY_IN_CART(HttpStatus.BAD_REQUEST, "USER400", "장바구니에 이미 해당상품이 존재합니다."), // Security 에러 INVALID_TOKEN(HttpStatus.BAD_REQUEST, "SEC4001", "잘못된 형식의 토큰입니다."), From b6308e76f17e66abc7f1675356dc79c3beebb030 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Tue, 13 Aug 2024 15:08:14 +0900 Subject: [PATCH 6/6] =?UTF-8?q?SCRUM-44=20feat:=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=EC=97=90=20=EB=8B=B4=EA=B2=A8=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=83=81=ED=92=88=EC=9D=80=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../golagola/domain/product/service/ProductService.java | 8 ++++++++ .../golagola/global/common/code/status/ErrorStatus.java | 1 + 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java index 490308c..5166ba8 100644 --- a/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java +++ b/src/main/java/com/kakaoteck/golagola/domain/product/service/ProductService.java @@ -100,7 +100,15 @@ public void deleteProduct(Seller seller, Long productId) { throw new GeneralException(ErrorStatus._UNAUTHORIZED_ACCESS); } + // 해당 Product가 장바구니에 담겨 있는지 확인 + boolean isInCart = product.getCartProducts() != null && !product.getCartProducts().isEmpty(); + + if (isInCart) { + throw new GeneralException(ErrorStatus._PRODUCT_IN_CART_CANNOT_DELETE); + } + // Product 삭제 productRepository.delete(product); } + } diff --git a/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java b/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java index c40e425..3849fbc 100644 --- a/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java +++ b/src/main/java/com/kakaoteck/golagola/global/common/code/status/ErrorStatus.java @@ -24,6 +24,7 @@ public enum ErrorStatus implements BaseErrorCode { // Product 에러 _NOT_FOUND_PRODUCT(HttpStatus.NOT_FOUND, "USER400", "제품이 존재하지 않습니다."), _NOT_FOUND_PRODUCT_IN_CART(HttpStatus.NOT_FOUND, "USER400", "제품이 장바구니에 존재하지 않습니다."), + _PRODUCT_IN_CART_CANNOT_DELETE(HttpStatus.BAD_REQUEST, "USER400", "장바구니에 담겨있는 상품은 삭제할 수 없습니다."), // Cart 에러 _CART_IS_ALREADY_EMPTY(HttpStatus.BAD_REQUEST, "USER400", "장바구니가 이미 비어있습니다."),