cart
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package com.example.project_it207_server.controller;
|
||||
|
||||
import com.example.project_it207_server.model.dto.request.AddToCartRequest;
|
||||
import com.example.project_it207_server.model.dto.request.UpdateCartItemRequest;
|
||||
import com.example.project_it207_server.model.dto.response.CartResponse;
|
||||
import com.example.project_it207_server.service.CartService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/cart")
|
||||
@RequiredArgsConstructor
|
||||
@CrossOrigin(origins = "*")
|
||||
public class CartController {
|
||||
|
||||
private final CartService cartService;
|
||||
|
||||
/**
|
||||
* Add product to cart
|
||||
* POST /api/cart/{userId}
|
||||
*/
|
||||
@PostMapping("/{userId}")
|
||||
public ResponseEntity<CartResponse> addToCart(
|
||||
@PathVariable Long userId,
|
||||
@Valid @RequestBody AddToCartRequest request) {
|
||||
CartResponse response = cartService.addToCart(userId, request);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cart by user ID
|
||||
* GET /api/cart/{userId}
|
||||
*/
|
||||
@GetMapping("/{userId}")
|
||||
public ResponseEntity<CartResponse> getCart(@PathVariable Long userId) {
|
||||
CartResponse response = cartService.getCartByUserId(userId);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cart item quantity
|
||||
* PUT /api/cart/{userId}/items/{cartItemId}
|
||||
*/
|
||||
@PutMapping("/{userId}/items/{cartItemId}")
|
||||
public ResponseEntity<CartResponse> updateCartItem(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Long cartItemId,
|
||||
@Valid @RequestBody UpdateCartItemRequest request) {
|
||||
CartResponse response = cartService.updateCartItem(userId, cartItemId, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from cart
|
||||
* DELETE /api/cart/{userId}/items/{cartItemId}
|
||||
*/
|
||||
@DeleteMapping("/{userId}/items/{cartItemId}")
|
||||
public ResponseEntity<CartResponse> removeCartItem(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Long cartItemId) {
|
||||
CartResponse response = cartService.removeCartItem(userId, cartItemId);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all items from cart
|
||||
* DELETE /api/cart/{userId}/clear
|
||||
*/
|
||||
@DeleteMapping("/{userId}/clear")
|
||||
public ResponseEntity<Void> clearCart(@PathVariable Long userId) {
|
||||
cartService.clearCart(userId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.project_it207_server.model.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AddToCartRequest {
|
||||
@NotNull(message = "Product ID is required")
|
||||
private Long productId;
|
||||
|
||||
@NotNull(message = "Quantity is required")
|
||||
@Min(value = 1, message = "Quantity must be at least 1")
|
||||
private Integer quantity;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.example.project_it207_server.model.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class UpdateCartItemRequest {
|
||||
@NotNull(message = "Quantity is required")
|
||||
@Min(value = 1, message = "Quantity must be at least 1")
|
||||
private Integer quantity;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import lombok.Setter;
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class AuthResponse {
|
||||
private Long userId;
|
||||
private String token;
|
||||
private String email;
|
||||
private Role role;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.project_it207_server.model.dto.response;
|
||||
|
||||
import lombok.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CartItemResponse {
|
||||
private Long cartItemId;
|
||||
private Long productId;
|
||||
private String productName;
|
||||
private String productImage;
|
||||
private Integer quantity;
|
||||
private BigDecimal price;
|
||||
private BigDecimal subtotal; // price * quantity
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.project_it207_server.model.dto.response;
|
||||
|
||||
import lombok.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CartResponse {
|
||||
private Long cartId;
|
||||
private Long userId;
|
||||
private List<CartItemResponse> items;
|
||||
private BigDecimal totalAmount; // Tổng tiền của tất cả items
|
||||
private Integer totalItems; // Tổng số lượng sản phẩm
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import java.math.BigDecimal;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
class CartItem {
|
||||
public class CartItem {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long cartItemId;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.example.project_it207_server.repository;
|
||||
|
||||
import com.example.project_it207_server.model.entity.Cart;
|
||||
import com.example.project_it207_server.model.entity.CartItem;
|
||||
import com.example.project_it207_server.model.entity.Product;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface CartItemRepository extends JpaRepository<CartItem, Long> {
|
||||
|
||||
@Query("SELECT ci FROM CartItem ci WHERE ci.cart = :cart AND ci.product = :product")
|
||||
List<CartItem> findAllByCartAndProduct(@Param("cart") Cart cart, @Param("product") Product product);
|
||||
|
||||
@Query("SELECT ci FROM CartItem ci WHERE ci.cart = :cart AND ci.product = :product ORDER BY ci.cartItemId ASC LIMIT 1")
|
||||
Optional<CartItem> findByCartAndProduct(@Param("cart") Cart cart, @Param("product") Product product);
|
||||
|
||||
void deleteAllByCart(Cart cart);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// CartRepository.java
|
||||
package com.example.project_it207_server.repository;
|
||||
|
||||
import com.example.project_it207_server.model.entity.Cart;
|
||||
import com.example.project_it207_server.model.entity.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface CartRepository extends JpaRepository<Cart, Long> {
|
||||
|
||||
// Thay đổi: Sử dụng List thay vì Optional để tránh lỗi
|
||||
@Query("SELECT c FROM Cart c WHERE c.user = :user ORDER BY c.createdAt DESC")
|
||||
List<Cart> findAllByUser(@Param("user") User user);
|
||||
|
||||
// Hoặc giữ nguyên nhưng thêm LIMIT 1
|
||||
@Query("SELECT c FROM Cart c WHERE c.user = :user ORDER BY c.createdAt DESC LIMIT 1")
|
||||
Optional<Cart> findByUser(@Param("user") User user);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public class AuthService {
|
||||
String redisKey = "token:" + newUser.getEmail() + ":" + token;
|
||||
redisService.setValueWithExpiry(redisKey, token, 1, TimeUnit.DAYS);// TTL 1 ngày
|
||||
|
||||
return new AuthResponse(token, newUser.getEmail(), newUser.getRole());
|
||||
return new AuthResponse(newUser.getUserId(), token, newUser.getEmail(), newUser.getRole());
|
||||
}
|
||||
|
||||
public AuthResponse login(AuthRequest req) {
|
||||
@@ -68,7 +68,7 @@ public class AuthService {
|
||||
String redisKey = "token:" + user.getEmail() + ":" + token;
|
||||
redisService.setValueWithExpiry(redisKey, token, 86400L, TimeUnit.SECONDS);
|
||||
|
||||
return new AuthResponse(token, user.getEmail(), user.getRole());
|
||||
return new AuthResponse(user.getUserId(), token, user.getEmail(), user.getRole());
|
||||
}
|
||||
|
||||
public void logout(String token) {
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.example.project_it207_server.service;
|
||||
|
||||
import com.example.project_it207_server.model.dto.request.AddToCartRequest;
|
||||
import com.example.project_it207_server.model.dto.request.UpdateCartItemRequest;
|
||||
import com.example.project_it207_server.model.dto.response.CartItemResponse;
|
||||
import com.example.project_it207_server.model.dto.response.CartResponse;
|
||||
import com.example.project_it207_server.model.entity.*;
|
||||
import com.example.project_it207_server.repository.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CartService {
|
||||
|
||||
private final CartRepository cartRepository;
|
||||
private final CartItemRepository cartItemRepository;
|
||||
private final ProductRepository productRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Transactional
|
||||
public CartResponse addToCart(Long userId, AddToCartRequest request) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
Product product = productRepository.findById(request.getProductId())
|
||||
.orElseThrow(() -> new RuntimeException("Product not found"));
|
||||
|
||||
// Get or create cart for user
|
||||
Cart cart = cartRepository.findByUser(user)
|
||||
.orElseGet(() -> {
|
||||
Cart newCart = Cart.builder()
|
||||
.user(user)
|
||||
.items(new ArrayList<>())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
return cartRepository.save(newCart);
|
||||
});
|
||||
|
||||
// Check if product already exists in cart
|
||||
// FIX: Xử lý trường hợp có nhiều CartItem trùng lặp
|
||||
List<CartItem> existingItems = cartItemRepository.findAllByCartAndProduct(cart, product);
|
||||
|
||||
CartItem cartItem;
|
||||
if (!existingItems.isEmpty()) {
|
||||
// Nếu có nhiều item trùng, lấy item đầu tiên và xóa các item còn lại
|
||||
cartItem = existingItems.get(0);
|
||||
|
||||
// Xóa các item trùng lặp (nếu có)
|
||||
for (int i = 1; i < existingItems.size(); i++) {
|
||||
cartItemRepository.delete(existingItems.get(i));
|
||||
}
|
||||
} else {
|
||||
// Tạo mới nếu chưa có
|
||||
cartItem = CartItem.builder()
|
||||
.cart(cart)
|
||||
.product(product)
|
||||
.quantity(0)
|
||||
.price(product.getPrice())
|
||||
.build();
|
||||
}
|
||||
|
||||
// Update quantity
|
||||
cartItem.setQuantity(cartItem.getQuantity() + request.getQuantity());
|
||||
cartItem.setPrice(product.getPrice());
|
||||
cartItemRepository.save(cartItem);
|
||||
|
||||
return getCartByUserId(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CartResponse getCartByUserId(Long userId) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
// FIX: Xử lý trường hợp có nhiều cart
|
||||
List<Cart> carts = cartRepository.findAllByUser(user);
|
||||
|
||||
Cart cart;
|
||||
if (carts.isEmpty()) {
|
||||
// Tạo cart mới nếu chưa có
|
||||
cart = Cart.builder()
|
||||
.user(user)
|
||||
.items(new ArrayList<>())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
cart = cartRepository.save(cart);
|
||||
} else {
|
||||
// Lấy cart mới nhất
|
||||
cart = carts.get(0);
|
||||
|
||||
// Xóa các cart cũ (nếu có nhiều cart)
|
||||
for (int i = 1; i < carts.size(); i++) {
|
||||
Cart oldCart = carts.get(i);
|
||||
cartItemRepository.deleteAllByCart(oldCart);
|
||||
cartRepository.delete(oldCart);
|
||||
}
|
||||
}
|
||||
|
||||
List<CartItemResponse> items = (cart.getItems() == null ? List.<CartItem>of() : cart.getItems())
|
||||
.stream()
|
||||
.map(this::mapToCartItemResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
BigDecimal totalAmount = items.stream()
|
||||
.map(CartItemResponse::getSubtotal)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
Integer totalItems = items.stream()
|
||||
.mapToInt(CartItemResponse::getQuantity)
|
||||
.sum();
|
||||
|
||||
return CartResponse.builder()
|
||||
.cartId(cart.getCartId())
|
||||
.userId(user.getUserId())
|
||||
.items(items)
|
||||
.totalAmount(totalAmount)
|
||||
.totalItems(totalItems)
|
||||
.createdAt(cart.getCreatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CartResponse updateCartItem(Long userId, Long cartItemId, UpdateCartItemRequest request) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
CartItem cartItem = cartItemRepository.findById(cartItemId)
|
||||
.orElseThrow(() -> new RuntimeException("Cart item not found"));
|
||||
|
||||
// Verify cart belongs to user
|
||||
if (!cartItem.getCart().getUser().getUserId().equals(user.getUserId())) {
|
||||
throw new RuntimeException("Cart item does not belong to user");
|
||||
}
|
||||
|
||||
cartItem.setQuantity(request.getQuantity());
|
||||
cartItemRepository.save(cartItem);
|
||||
|
||||
return getCartByUserId(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CartResponse removeCartItem(Long userId, Long cartItemId) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
CartItem cartItem = cartItemRepository.findById(cartItemId)
|
||||
.orElseThrow(() -> new RuntimeException("Cart item not found"));
|
||||
|
||||
// Verify cart belongs to user
|
||||
if (!cartItem.getCart().getUser().getUserId().equals(user.getUserId())) {
|
||||
throw new RuntimeException("Cart item does not belong to user");
|
||||
}
|
||||
|
||||
// Xóa item
|
||||
cartItemRepository.delete(cartItem);
|
||||
cartItemRepository.flush(); // Đảm bảo xóa ngay lập tức
|
||||
|
||||
System.out.println("=== REMOVED CART ITEM: " + cartItemId + " ===");
|
||||
|
||||
// Trả về cart đã cập nhật
|
||||
CartResponse response = getCartByUserId(userId);
|
||||
System.out.println("=== CART AFTER REMOVE - Total items: " + response.getTotalItems() + " ===");
|
||||
return response;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void clearCart(Long userId) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
Cart cart = cartRepository.findByUser(user)
|
||||
.orElseThrow(() -> new RuntimeException("Cart not found"));
|
||||
|
||||
cartItemRepository.deleteAllByCart(cart);
|
||||
}
|
||||
|
||||
private CartItemResponse mapToCartItemResponse(CartItem cartItem) {
|
||||
BigDecimal subtotal = cartItem.getPrice()
|
||||
.multiply(BigDecimal.valueOf(cartItem.getQuantity()));
|
||||
|
||||
return CartItemResponse.builder()
|
||||
.cartItemId(cartItem.getCartItemId())
|
||||
.productId(cartItem.getProduct().getProductId())
|
||||
.productName(cartItem.getProduct().getProductName())
|
||||
.productImage(cartItem.getProduct().getImageUrl())
|
||||
.quantity(cartItem.getQuantity())
|
||||
.price(cartItem.getPrice())
|
||||
.subtotal(subtotal)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user