diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/base/exception/OutOfStockException.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/base/exception/OutOfStockException.java new file mode 100644 index 0000000..52371f8 --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/base/exception/OutOfStockException.java @@ -0,0 +1,12 @@ +package com.lcaohoanq.shoppe.base.exception; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class OutOfStockException extends RuntimeException{ + + public OutOfStockException(String message) { + super(message); + } + +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/config/WebSecurityConfig.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/config/WebSecurityConfig.java index 4d7c54a..b427c57 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/config/WebSecurityConfig.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/config/WebSecurityConfig.java @@ -60,6 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { String.format("%s/orders-details/**", apiPrefix), String.format("%s/orders/**", apiPrefix), String.format("%s/assets/**", apiPrefix), + String.format("%s/carts/**", apiPrefix), "/error" ).permitAll() // Swagger UI with basic auth diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/auth/AuthService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/auth/AuthService.java index 17527eb..ae02ea5 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/auth/AuthService.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/auth/AuthService.java @@ -1,17 +1,16 @@ package com.lcaohoanq.shoppe.domain.auth; -import com.github.javafaker.Faker; import com.lcaohoanq.shoppe.component.JwtTokenUtils; import com.lcaohoanq.shoppe.component.LocalizationUtils; import com.lcaohoanq.shoppe.constant.Regex; import com.lcaohoanq.shoppe.domain.cart.Cart; -import com.lcaohoanq.shoppe.domain.cart.CartProduct; +import com.lcaohoanq.shoppe.domain.cart.CartItem; import com.lcaohoanq.shoppe.domain.cart.CartRepository; -import com.lcaohoanq.shoppe.domain.role.Role; +import com.lcaohoanq.shoppe.domain.cart.CartResponse; +import com.lcaohoanq.shoppe.domain.cart.CartService; import com.lcaohoanq.shoppe.domain.user.UserResponse; import com.lcaohoanq.shoppe.enums.Country; import com.lcaohoanq.shoppe.enums.Currency; -import com.lcaohoanq.shoppe.enums.Gender; import com.lcaohoanq.shoppe.enums.UserStatus; import com.lcaohoanq.shoppe.exception.ExpiredTokenException; import com.lcaohoanq.shoppe.exception.MalformBehaviourException; @@ -36,10 +35,8 @@ import jakarta.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Random; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; @@ -71,11 +68,12 @@ public class AuthService implements IAuthService, DTOConverter { private final UserService userService; private final WalletRepository walletRepository; private final CartRepository cartRepository; + private final CartService cartService; @Override @Transactional public User register(AccountRegisterDTO accountRegisterDTO) throws Exception { - + if (!accountRegisterDTO.password().matches(Regex.PASSWORD_REGEX)) { throw new PasswordWrongFormatException( "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"); @@ -89,7 +87,7 @@ public User register(AccountRegisterDTO accountRegisterDTO) throws Exception { if (userRepository.existsByEmail(email)) { throw new DataIntegrityViolationException("Email already exists"); } - + if (userRepository.existsByPhoneNumber(accountRegisterDTO.phoneNumber())) { throw new DataIntegrityViolationException("Phone number already exists"); } @@ -98,13 +96,15 @@ public User register(AccountRegisterDTO accountRegisterDTO) throws Exception { ((ServletRequestAttributes) Objects.requireNonNull( RequestContextHolder.getRequestAttributes())).getRequest(); String acceptLanguage = request.getHeader("Accept-Language"); - String preferredLanguage = String.valueOf(Optional.ofNullable(accountRegisterDTO.preferredLanguage()) - .orElse(acceptLanguage == null || acceptLanguage.isEmpty() - ? Country.UNITED_STATES - : Country.valueOf(acceptLanguage.toUpperCase()))); - - String preferredCurrency = String.valueOf(Optional.ofNullable(accountRegisterDTO.preferredCurrency()) - .orElse(Currency.USD)); + String preferredLanguage = String.valueOf( + Optional.ofNullable(accountRegisterDTO.preferredLanguage()) + .orElse(acceptLanguage == null || acceptLanguage.isEmpty() + ? Country.UNITED_STATES + : Country.valueOf(acceptLanguage.toUpperCase()))); + + String preferredCurrency = String.valueOf( + Optional.ofNullable(accountRegisterDTO.preferredCurrency()) + .orElse(Currency.USD)); return Single.fromCallable(() -> { User newUser = User.builder() @@ -135,23 +135,14 @@ public User register(AccountRegisterDTO accountRegisterDTO) throws Exception { .balance(0F) .user(newUser) // Set the saved user .build(); - - newWallet = walletRepository.save(newWallet); - - Cart newCart = Cart.builder() - .user(newUser) - .cartProducts(new ArrayList<>()) - .build(); - CartProduct cartProduct = CartProduct.builder() - .cart(newCart) - .quantity(0) - .build(); + newWallet = walletRepository.save(newWallet); - newCart.addCartProduct(cartProduct); - newCart = cartRepository.save(newCart); + Cart newCart = cartRepository.findById + (cartService.create(newUser.getId()).id()) + .orElseThrow(() -> new DataNotFoundException("Cart not found")); - // Step 3: Set the wallet on the user and save the user again + // Step 3: Set the wallet on the user and save the user again newUser.setWallet(newWallet); newUser.setCart(newCart); userRepository.save(newUser); @@ -171,7 +162,7 @@ public String login(String email, String password) throws Exception { localizationUtils.getLocalizedMessage(MessageKey.WRONG_PHONE_PASSWORD)); } User existingUser = optionalUser.get(); - + existingUser.setLastLoginTimestamp(LocalDateTime.now()); userRepository.save(existingUser); diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/AddNewCartDTO.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/AddNewCartDTO.java new file mode 100644 index 0000000..c6b4eff --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/AddNewCartDTO.java @@ -0,0 +1,10 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; + +public record AddNewCartDTO( + @JsonProperty("user_id") + @NotNull(message = "User id is required when creating a new cart") + Long userId +) {} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/Cart.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/Cart.java index 560f2e8..8f9760d 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/Cart.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/Cart.java @@ -51,20 +51,20 @@ public class Cart extends BaseEntity { private User user; @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) - private List cartProducts = new ArrayList<>(); + private List cartItems = new ArrayList<>(); - public void addCartProduct(CartProduct cartProduct) { - if (cartProducts == null) { - cartProducts = new ArrayList<>(); + public void addCartProduct(CartItem cartItem) { + if (cartItems == null) { + cartItems = new ArrayList<>(); } - cartProducts.add(cartProduct); - cartProduct.setCart(this); + cartItems.add(cartItem); + cartItem.setCart(this); } - public void removeCartProduct(CartProduct cartProduct) { - if (cartProducts != null) { - cartProducts.remove(cartProduct); - cartProduct.setCart(null); + public void removeCartProduct(CartItem cartItem) { + if (cartItems != null) { + cartItems.remove(cartItem); + cartItem.setCart(null); } } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartController.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartController.java new file mode 100644 index 0000000..aff7593 --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartController.java @@ -0,0 +1,69 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.lcaohoanq.shoppe.api.ApiResponse; +import com.lcaohoanq.shoppe.domain.user.IUserService; +import com.lcaohoanq.shoppe.domain.user.User; +import com.lcaohoanq.shoppe.exception.MethodArgumentNotValidException; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("${api.prefix}/carts") +@Slf4j +@RequiredArgsConstructor +public class CartController { + + private final ICartItemService cartItemService; + private final IUserService userService; + + @GetMapping("") + public ResponseEntity> getAll( + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "limit", defaultValue = "10") int limit + ) { + return ResponseEntity.ok( + ApiResponse.builder() + .data(null) + .build() + ); + } + + @PostMapping("/item") + @PreAuthorize("hasRole('ROLE_MEMBER')") + public ResponseEntity> addItem( + @Valid @RequestBody CartItemDTO cartItemDTO, + BindingResult result + ) { + + if (result.hasErrors()) { + throw new MethodArgumentNotValidException(result); + } + + UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + User user = userService.findByUsername(userDetails.getUsername()); + + return ResponseEntity.ok( + ApiResponse.builder() + .message("Add item to cart successfully") + .statusCode(HttpStatus.OK.value()) + .isSuccess(true) + .data(cartItemService.createCartItem(user.getId(),cartItemDTO)) + .build() + ); + } + +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartProduct.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItem.java similarity index 94% rename from SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartProduct.java rename to SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItem.java index 5e14163..464386e 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartProduct.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItem.java @@ -19,13 +19,13 @@ import lombok.Setter; @Entity -@Table(name = "cart_products") +@Table(name = "cart_items") @Getter @Setter @Builder @AllArgsConstructor @NoArgsConstructor -public class CartProduct extends BaseEntity { +public class CartItem extends BaseEntity { @Id @SequenceGenerator(name = "cartproducts_seq", sequenceName = "cartproducts_id_seq", allocationSize = 1) diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemDTO.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemDTO.java index b3c8c97..2b26946 100755 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemDTO.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemDTO.java @@ -1,10 +1,15 @@ package com.lcaohoanq.shoppe.domain.cart; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; public record CartItemDTO( @JsonProperty("product_id") - Long productId, + @Min(value = 1, message = "Product id must be greater than 0") + @NotNull(message = "Product id is required") Long productId, + @JsonProperty("quantity") - Integer quantity + @Min(value = 1, message = "Quantity must be greater than 0") + @NotNull(message = "Quantity is required") Integer quantity ) {} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemRepository.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemRepository.java new file mode 100644 index 0000000..a3b9737 --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemRepository.java @@ -0,0 +1,13 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CartItemRepository extends JpaRepository { + + List findByCartId(Long cartId); + + Optional findByCartIdAndProductId(long cartId, long productId); + +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemResponse.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemResponse.java new file mode 100644 index 0000000..65cc49f --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemResponse.java @@ -0,0 +1,26 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.lcaohoanq.shoppe.domain.product.Product; +import com.lcaohoanq.shoppe.domain.product.ProductResponse; +import java.time.LocalDateTime; + +public record CartItemResponse( + @JsonProperty("id") + Long id, + @JsonProperty("cart_id") + Long cartId, + @JsonProperty("product") + ProductResponse product, + @JsonProperty("quantity") + int quantity, + + @JsonProperty("created_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + LocalDateTime createdAt, + + @JsonProperty("updated_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + LocalDateTime updatedAt +) {} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemService.java new file mode 100644 index 0000000..6c64572 --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartItemService.java @@ -0,0 +1,124 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.lcaohoanq.shoppe.base.exception.DataNotFoundException; +import com.lcaohoanq.shoppe.base.exception.OutOfStockException; +import com.lcaohoanq.shoppe.domain.product.IProductService; +import com.lcaohoanq.shoppe.domain.product.Product; +import com.lcaohoanq.shoppe.domain.product.ProductRepository; +import com.lcaohoanq.shoppe.domain.product.ProductResponse; +import com.lcaohoanq.shoppe.domain.user.IUserService; +import com.lcaohoanq.shoppe.exception.MalformBehaviourException; +import com.lcaohoanq.shoppe.util.DTOConverter; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CartItemService implements ICartItemService, DTOConverter { + + private final IUserService userService; + private final ICartService cartService; + private final IProductService productService; + private final CartItemRepository cartItemRepository; + private final CartRepository cartRepository; + private final ProductRepository productRepository; + + @Override + public CartItemResponse createCartItem(long userId, CartItemDTO cartItemDTO) { + + // Validate user existence + if (!userService.existsById(userId)) { + throw new MalformBehaviourException("User with id " + userId + " does not exist"); + } + + // Get the existing cart for the user + Cart existedCart = cartRepository + .findByUserId(userId) + .orElseThrow(() -> + new DataNotFoundException( + "Cart with user id " + userId + " does not exist")); + + // Validate product existence + Product existProduct = productRepository + .findById(cartItemDTO.productId()) + .orElseThrow(() -> + new DataNotFoundException("Product with id " + cartItemDTO.productId() + " does not exist")); + + ProductResponse product = productService.getById(cartItemDTO.productId()); + + if (cartItemDTO.quantity() >= product.quantity()) { + throw new OutOfStockException(String.format( + "Product with id %d has only %d items left in stock", + cartItemDTO.productId(), product.quantity() + )); + } + + // Check if the product already exists in the cart + Optional existingCartItemOpt = cartItemRepository.findByCartIdAndProductId( + existedCart.getId(), cartItemDTO.productId() + ); + + CartItem cartItem; + + if (existingCartItemOpt.isPresent()) { + // Product already exists in the cart, update the quantity + cartItem = existingCartItemOpt.get(); + cartItem.setQuantity(cartItem.getQuantity() + cartItemDTO.quantity()); + } else { + // Product does not exist, create a new CartItem + cartItem = CartItem.builder() + .cart(existedCart) + .product(existProduct) + .quantity(cartItemDTO.quantity()) + .build(); + } + + // Update product stock and cart total quantity + productService.updateQuantity(cartItemDTO.productId(), cartItemDTO.quantity(), false); + cartService.updateQuantity(existedCart.getId(), cartItemDTO.quantity(), true); + + return toCartItemResponse(cartItemRepository.save(cartItem)); + } + + @Override + public CartItemDTO updateCartItem(CartItemDTO cartItemDTO) { + return null; + } + + @Override + public void deleteCartItem(Long cartItemId) { + + } + + @Override + public CartItemDTO getCartItem(Long cartItemId) { + return null; + } + + @Override + public List getCartItems() { + return List.of(); + } + + @Override + public void deleteAllCartItems() { + + } + + @Override + public void deleteCartItemsByCartId(Long cartId) { + + } + + @Override + public List getCartItemsByCartId(Long cartId) { + return List.of(); + } + + @Override + public void deleteCartItemsByProductId(Long productId) { + + } +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartRepository.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartRepository.java index bd7fcb2..ecb37ca 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartRepository.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartRepository.java @@ -1,7 +1,29 @@ package com.lcaohoanq.shoppe.domain.cart; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CartRepository extends JpaRepository { + + Boolean existsByUserId(Long userId); + + Optional findByUserId(Long userId); + + @Modifying + @Query("UPDATE Cart c SET c.totalQuantity = c.totalQuantity + :quantity WHERE c.id = :productId") + void increase( + @Param("productId") Long cartId, + @Param("quantity") Integer quantity + ); + + @Modifying + @Query("UPDATE Cart c SET c.totalQuantity = c.totalQuantity - :quantity WHERE c.id = :productId") + void decrease( + @Param("productId") Long cartId, + @Param("quantity") Integer quantity + ); } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartResponse.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartResponse.java new file mode 100644 index 0000000..32ed7ab --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartResponse.java @@ -0,0 +1,35 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record CartResponse( + @JsonProperty("id") + Long id, + + @JsonProperty("total_quantity") + Integer totalQuantity, + + @JsonProperty("total_price") + Double totalPrice, + + @JsonProperty("user_id") + @NotNull(message = "User id is required when creating a new cart") + Long userId, + + @JsonProperty("cart_items") + List cartItems, + + @JsonProperty("created_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + LocalDateTime createdAt, + + @JsonProperty("updated_at") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + LocalDateTime updatedAt +) {} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartService.java new file mode 100644 index 0000000..6abb19b --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/CartService.java @@ -0,0 +1,114 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.lcaohoanq.shoppe.api.PageResponse; +import com.lcaohoanq.shoppe.domain.user.IUserService; +import com.lcaohoanq.shoppe.domain.user.User; +import com.lcaohoanq.shoppe.exception.MalformBehaviourException; +import com.lcaohoanq.shoppe.util.DTOConverter; +import java.util.ArrayList; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.security.core.parameters.P; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CartService implements ICartService, DTOConverter { + + private final IUserService userService; + private final CartRepository cartRepository; + + @Override + public CartResponse create(long userId) { + + if(!userService.existsById(userId)){ + throw new MalformBehaviourException("User with id: " + userId + " not found"); + } + + if(cartRepository.existsByUserId(userId)){ + throw new MalformBehaviourException("Cart existed for user with id: " + userId); + } + + User existedUser = userService.findUserById(userId); + + Cart newCart = Cart.builder() + .user(existedUser) + .totalPrice(0) + .totalQuantity(0) + .cartItems(new ArrayList<>()) + .build(); + + CartItem cartItem = CartItem.builder() + .cart(newCart) + .quantity(0) + .build(); + + newCart.addCartProduct(cartItem); + + return toCartResponse(cartRepository.save(newCart)); + + } + + @Override + public PageResponse getAllCarts(Pageable pageable) { + return null; + } + + @Override + public CartResponse findById(Long cartId) { + return toCartResponse(cartRepository + .findById(cartId) + .orElseThrow(() -> new MalformBehaviourException("Cart with id: " + cartId + " not found"))); + } + + @Override + public CartResponse addItemToCart(Long cartId, Long productId, int quantity) { + return null; + } + + @Override + public CartResponse removeItemFromCart(Long cartId, Long productId) { + return null; + } + + @Override + public CartResponse updateItemInCart(Long cartId, Long productId, int quantity) { + return null; + } + + @Override + public CartResponse clear(Long cartId) { + return null; + } + + @Override + public void purchaseAllCart(Long cartId) { + + } + + @Override + public void purchaseItem(Long cartId, Long productId) { + + } + + @Override + public void purchaseItems(Long cartId, Long[] productIds) { + + } + + @Override + public Boolean existsById(Long cartId) { + return cartRepository.existsById(cartId); + } + + @Override + @Transactional + public void updateQuantity(Long cartId, Integer quantity, boolean isIncrease) { + if(isIncrease){ + cartRepository.increase(cartId, quantity); + } else { + cartRepository.decrease(cartId, quantity); + } + } +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/ICartItemService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/ICartItemService.java new file mode 100644 index 0000000..272e078 --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/ICartItemService.java @@ -0,0 +1,25 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import java.util.List; + +public interface ICartItemService { + + CartItemResponse createCartItem(long userId, CartItemDTO cartItemDTO); + + CartItemDTO updateCartItem(CartItemDTO cartItemDTO); + + void deleteCartItem(Long cartItemId); + + CartItemDTO getCartItem(Long cartItemId); + + List getCartItems(); + + void deleteAllCartItems(); + + void deleteCartItemsByCartId(Long cartId); + + List getCartItemsByCartId(Long cartId); + + void deleteCartItemsByProductId(Long productId); + +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/ICartService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/ICartService.java new file mode 100644 index 0000000..a1070be --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/cart/ICartService.java @@ -0,0 +1,30 @@ +package com.lcaohoanq.shoppe.domain.cart; + +import com.lcaohoanq.shoppe.api.PageResponse; +import org.springframework.data.domain.Pageable; + +public interface ICartService { + CartResponse create(long userId); + + PageResponse getAllCarts(Pageable pageable); + + CartResponse findById(Long cartId); + + CartResponse addItemToCart(Long cartId, Long productId, int quantity); + + CartResponse removeItemFromCart(Long cartId, Long productId); + + CartResponse updateItemInCart(Long cartId, Long productId, int quantity); + + CartResponse clear(Long cartId); + + void purchaseAllCart(Long cartId); + + void purchaseItem(Long cartId, Long productId); + + void purchaseItems(Long cartId, Long[] productIds); + + Boolean existsById(Long cartId); + + void updateQuantity(Long cartId, Integer quantity, boolean isIncrease); +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/IProductService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/IProductService.java index c998ce1..7ce85c9 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/IProductService.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/IProductService.java @@ -3,6 +3,7 @@ import com.lcaohoanq.shoppe.api.PageResponse; import com.lcaohoanq.shoppe.metadata.MediaMeta; import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.annotation.Transactional; public interface IProductService { @@ -18,4 +19,8 @@ public interface IProductService { ProductImage createProductImage(Long productId, MediaMeta mediaMeta, ProductImageDTO productImageDTO) throws Exception; + + Boolean existsById(Long id); + + void updateQuantity(long productId, int quantity, boolean isIncrease); } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/Product.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/Product.java index 9f70bfc..a69acd0 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/Product.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/Product.java @@ -1,7 +1,7 @@ package com.lcaohoanq.shoppe.domain.product; import com.fasterxml.jackson.annotation.JsonProperty; -import com.lcaohoanq.shoppe.domain.cart.CartProduct; +import com.lcaohoanq.shoppe.domain.cart.CartItem; import com.lcaohoanq.shoppe.domain.category.Category; import com.lcaohoanq.shoppe.enums.ProductStatus; import com.lcaohoanq.shoppe.domain.user.User; @@ -73,6 +73,6 @@ public class Product extends BaseEntity { private List images = new ArrayList<>(); @OneToMany(mappedBy = "product") - private List cartProducts = new ArrayList<>(); + private List cartItems = new ArrayList<>(); } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductRepository.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductRepository.java index 0664294..375c25b 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductRepository.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductRepository.java @@ -1,14 +1,32 @@ package com.lcaohoanq.shoppe.domain.product; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ProductRepository extends JpaRepository { boolean existsByName(String name); - + @Modifying @Query("UPDATE Product p SET p.isActive = :isActive WHERE p.id = :id") void updateProductIsActive(Long id, boolean isActive); + + @Modifying + @Query("UPDATE Product p SET p.quantity = p.quantity + :quantity WHERE p.id = :productId") + void increaseProductQuantity( + @Param("productId") long productId, + @Param("quantity") int quantity + ); + + @Modifying + @Query("UPDATE Product p SET p.quantity = p.quantity - :quantity WHERE p.id = :productId") + void decreaseProductQuantity( + @Param("productId") long productId, + @Param("quantity") int quantity + ); + + } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductService.java index f90deea..1c4aa28 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductService.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/product/ProductService.java @@ -1,16 +1,16 @@ package com.lcaohoanq.shoppe.domain.product; import com.lcaohoanq.shoppe.api.PageResponse; +import com.lcaohoanq.shoppe.base.exception.DataNotFoundException; +import com.lcaohoanq.shoppe.domain.category.Category; +import com.lcaohoanq.shoppe.domain.category.CategoryRepository; +import com.lcaohoanq.shoppe.domain.user.User; +import com.lcaohoanq.shoppe.domain.user.UserRepository; import com.lcaohoanq.shoppe.enums.ProductStatus; import com.lcaohoanq.shoppe.exception.CategoryNotFoundException; import com.lcaohoanq.shoppe.exception.InvalidParamException; import com.lcaohoanq.shoppe.exception.MalformBehaviourException; -import com.lcaohoanq.shoppe.base.exception.DataNotFoundException; import com.lcaohoanq.shoppe.metadata.MediaMeta; -import com.lcaohoanq.shoppe.domain.category.Category; -import com.lcaohoanq.shoppe.domain.user.User; -import com.lcaohoanq.shoppe.domain.category.CategoryRepository; -import com.lcaohoanq.shoppe.domain.user.UserRepository; import com.lcaohoanq.shoppe.util.DTOConverter; import com.lcaohoanq.shoppe.util.PaginationConverter; import java.util.Optional; @@ -113,4 +113,19 @@ public ProductImage createProductImage(Long productId, MediaMeta mediaMeta, Prod return productImageRepository.save(newProductImage); } + @Override + public Boolean existsById(Long id) { + return productRepository.existsById(id); + } + + @Transactional + @Override + public void updateQuantity(long productId, int quantity, boolean isIncrease) { + if (isIncrease) { + productRepository.increaseProductQuantity(productId, quantity); + } else { + productRepository.decreaseProductQuantity(productId, quantity); + } + } + } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/IUserService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/IUserService.java index bb77762..283ca06 100755 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/IUserService.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/IUserService.java @@ -49,4 +49,6 @@ public interface IUserService { Boolean existsByEmail(String email); Boolean existsByPhoneNumber(String phoneNumber); + + Boolean existsById(Long id); } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/UserService.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/UserService.java index 2889694..09043e4 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/UserService.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/domain/user/UserService.java @@ -368,4 +368,9 @@ public Boolean existsByPhoneNumber(String phoneNumber) { return userRepository.existsByPhoneNumber(phoneNumber); } + @Override + public Boolean existsById(Long id) { + return userRepository.existsById(id); + } + } diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/GlobalExceptionHandler.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/GlobalExceptionHandler.java index 2222065..4e228c2 100644 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/GlobalExceptionHandler.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.lcaohoanq.shoppe.exception; +import com.lcaohoanq.shoppe.base.exception.OutOfStockException; import com.lcaohoanq.shoppe.component.LocalizationUtils; import com.lcaohoanq.shoppe.api.ApiError; import com.lcaohoanq.shoppe.base.exception.DataAlreadyExistException; @@ -47,6 +48,17 @@ public ApiError handleDataNotFoundException(DataNotFoundException e) { .build(); } + @ExceptionHandler(OutOfStockException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiError handleOutOfStockException(OutOfStockException e) { + return ApiError.errorBuilder() + .message("Out of stock") + .reason(e.getMessage()) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .isSuccess(false) + .build(); + } + @ExceptionHandler(IOException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ApiError handleIOException(IOException e) { diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/ProductOutOfStockException.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/ProductOutOfStockException.java new file mode 100644 index 0000000..68251ab --- /dev/null +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/exception/ProductOutOfStockException.java @@ -0,0 +1,9 @@ +package com.lcaohoanq.shoppe.exception; + +import com.lcaohoanq.shoppe.base.exception.OutOfStockException; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ProductOutOfStockException extends OutOfStockException { + +} diff --git a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/util/DTOConverter.java b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/util/DTOConverter.java index 6a08215..1cd3667 100755 --- a/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/util/DTOConverter.java +++ b/SPCServer/springboot/src/main/java/com/lcaohoanq/shoppe/util/DTOConverter.java @@ -1,20 +1,24 @@ package com.lcaohoanq.shoppe.util; -import com.lcaohoanq.shoppe.domain.category.CategoryResponse; -import com.lcaohoanq.shoppe.domain.order.OrderDetailResponse; -import com.lcaohoanq.shoppe.domain.order.OrderResponse; -import com.lcaohoanq.shoppe.domain.product.ProductImageResponse; -import com.lcaohoanq.shoppe.domain.product.ProductResponse; -import com.lcaohoanq.shoppe.domain.role.RoleResponse; -import com.lcaohoanq.shoppe.domain.user.UserResponse; +import com.lcaohoanq.shoppe.domain.cart.Cart; +import com.lcaohoanq.shoppe.domain.cart.CartItem; +import com.lcaohoanq.shoppe.domain.cart.CartItemResponse; +import com.lcaohoanq.shoppe.domain.cart.CartResponse; import com.lcaohoanq.shoppe.domain.category.Category; +import com.lcaohoanq.shoppe.domain.category.CategoryResponse; +import com.lcaohoanq.shoppe.domain.category.Subcategory; import com.lcaohoanq.shoppe.domain.order.Order; import com.lcaohoanq.shoppe.domain.order.OrderDetail; +import com.lcaohoanq.shoppe.domain.order.OrderDetailResponse; +import com.lcaohoanq.shoppe.domain.order.OrderResponse; import com.lcaohoanq.shoppe.domain.product.Product; import com.lcaohoanq.shoppe.domain.product.ProductImage; +import com.lcaohoanq.shoppe.domain.product.ProductImageResponse; +import com.lcaohoanq.shoppe.domain.product.ProductResponse; import com.lcaohoanq.shoppe.domain.role.Role; -import com.lcaohoanq.shoppe.domain.category.Subcategory; +import com.lcaohoanq.shoppe.domain.role.RoleResponse; import com.lcaohoanq.shoppe.domain.user.User; +import com.lcaohoanq.shoppe.domain.user.UserResponse; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -135,5 +139,28 @@ default OrderDetailResponse toOrderDetailResponse(OrderDetail orderDetail) { .totalMoney(orderDetail.getTotalMoney()) .build(); } + + default CartResponse toCartResponse(Cart cart){ + return new CartResponse( + cart.getId(), + cart.getTotalQuantity(), + cart.getTotalPrice(), + cart.getUser().getId(), + cart.getCartItems(), + cart.getCreatedAt(), + cart.getUpdatedAt() + ); + } + + default CartItemResponse toCartItemResponse(CartItem cartItem){ + return new CartItemResponse( + cartItem.getId(), + cartItem.getCart().getId(), + this.toProductResponse(cartItem.getProduct()), + cartItem.getQuantity(), + cartItem.getCreatedAt(), + cartItem.getUpdatedAt() + ); + } } diff --git a/SPCServer/springboot/uploads/ee7dc957-1e38-4dd4-9cb9-6b56e5fe363f_DSCF1023.JPG b/SPCServer/springboot/uploads/ee7dc957-1e38-4dd4-9cb9-6b56e5fe363f_DSCF1023.JPG new file mode 100644 index 0000000..f2aedfd Binary files /dev/null and b/SPCServer/springboot/uploads/ee7dc957-1e38-4dd4-9cb9-6b56e5fe363f_DSCF1023.JPG differ