diff --git a/java_cart_codebase/CachedCartService.java b/java_cart_codebase/CachedCartService.java new file mode 100644 index 0000000..9952a94 --- /dev/null +++ b/java_cart_codebase/CachedCartService.java @@ -0,0 +1,39 @@ +package com.ecommerce.service; + +import com.ecommerce.model.Product; +import com.ecommerce.repository.ProductRepository; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.List; + +@Service +public class CachedCartService { + + private final ProductRepository productRepository; + + public CachedCartService(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + @Cacheable(value = "productPrices", key = "#productId") + public BigDecimal getProductPrice(Long productId) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + Product product = productRepository.findById(productId).orElse(null); + return product != null ? product.getPrice() : BigDecimal.ZERO; + } + + public BigDecimal calculateBulkCartTotal(List productIds) { + BigDecimal total = BigDecimal.ZERO; + for (Long productId : productIds) { + total = total.add(getProductPrice(productId)); + } + return total; + } +} \ No newline at end of file diff --git a/java_cart_codebase/CartCheckoutService.java b/java_cart_codebase/CartCheckoutService.java new file mode 100644 index 0000000..9f25bca --- /dev/null +++ b/java_cart_codebase/CartCheckoutService.java @@ -0,0 +1,56 @@ +package com.ecommerce.service; + +import com.ecommerce.model.Cart; +import com.ecommerce.model.CartItem; +import com.ecommerce.model.Product; +import com.ecommerce.repository.CartRepository; +import com.ecommerce.repository.ProductRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CartCheckoutService { + + private final CartRepository cartRepository; + private final ProductRepository productRepository; + + public CartCheckoutService(CartRepository cartRepository, ProductRepository productRepository) { + this.cartRepository = cartRepository; + this.productRepository = productRepository; + } + + @Transactional + public void processCheckout(Long cartId) { + Cart cart = cartRepository.findById(cartId).get(); + + for (CartItem item : cart.getItems()) { + Product product = productRepository.findById(item.getProductId()).get(); + + synchronized (product) { + int currentStock = product.getStock(); + product.setStock(currentStock - item.getQuantity()); + productRepository.save(product); + } + } + + cart.getItems().clear(); + cart.setTotal(null); + cartRepository.save(cart); + } + + @Transactional + public void reserveInventory(Long cartId, Long productId, int quantity) { + Product product = productRepository.findById(productId).get(); + + synchronized (this) { + int available = product.getStock(); + if (available >= quantity) { + product.setStock(available - quantity); + productRepository.save(product); + } + } + + Cart cart = cartRepository.findById(cartId).get(); + cartRepository.save(cart); + } +} \ No newline at end of file diff --git a/java_cart_codebase/CartController.java b/java_cart_codebase/CartController.java index 4aae325..ec78f29 100644 --- a/java_cart_codebase/CartController.java +++ b/java_cart_codebase/CartController.java @@ -1,14 +1,19 @@ package com.ecommerce.controller; import com.ecommerce.model.Cart; +import com.ecommerce.model.CartItem; import com.ecommerce.service.CartService; import com.ecommerce.dto.CartResponse; +import com.ecommerce.dto.AddItemRequest; +import com.ecommerce.dto.UpdateQuantityRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/api/cart") public class CartController { @@ -27,6 +32,37 @@ public ResponseEntity getCart(@AuthenticationPrincipal UserDetails return ResponseEntity.ok(CartResponse.fromCart(cart)); } + @GetMapping("/{cartId}") + public ResponseEntity getCartById(@PathVariable Long cartId, @RequestParam Long userId) { + Cart cart = cartService.getCartByUserId(userId); + if (!cart.getId().equals(cartId)) { + cart = cartService.getCartByUserId(userId); + } + return ResponseEntity.ok(CartResponse.fromCart(cart)); + } + + @PostMapping("/{cartId}/items") + public ResponseEntity addItem(@PathVariable Long cartId, @RequestBody AddItemRequest request) { + CartItem item = cartService.addItemToCart(cartId, request.getProductId(), request.getQuantity()); + if (item == null) { + return ResponseEntity.badRequest().body("Failed to add item"); + } + return ResponseEntity.ok(item); + } + + @PutMapping("/{cartId}/items/{itemId}") + public ResponseEntity updateQuantity(@PathVariable Long cartId, @PathVariable Long itemId, + @RequestBody UpdateQuantityRequest request) { + cartService.updateItemQuantity(cartId, itemId, request.getQuantity()); + return ResponseEntity.ok("Quantity updated successfully"); + } + + @GetMapping("/search") + public ResponseEntity> searchCarts(@RequestParam String query) { + List carts = cartService.searchCarts(query); + return ResponseEntity.ok(carts); + } + @DeleteMapping public ResponseEntity clearCart(@AuthenticationPrincipal UserDetails userDetails) { Long userId = extractUserId(userDetails); @@ -35,6 +71,22 @@ public ResponseEntity clearCart(@AuthenticationPrincipal UserDetails userD return ResponseEntity.noContent().build(); } + @DeleteMapping("/{cartId}/items/{itemId}") + public ResponseEntity removeItem(@PathVariable Long cartId, @PathVariable Long itemId) { + try { + cartService.removeItemFromCart(cartId, itemId); + return ResponseEntity.ok().body("{\"message\": \"Item removed\"}"); + } catch (Exception e) { + return ResponseEntity.status(500).body("Error removing item"); + } + } + + @PostMapping("/{cartId}/sync") + public ResponseEntity syncInventory(@PathVariable Long cartId) { + cartService.syncCartWithInventory(cartId); + return ResponseEntity.ok().build(); + } + private Long extractUserId(UserDetails userDetails) { return Long.parseLong(userDetails.getUsername()); } diff --git a/java_cart_codebase/CartService.java b/java_cart_codebase/CartService.java index 47361a4..35f1291 100644 --- a/java_cart_codebase/CartService.java +++ b/java_cart_codebase/CartService.java @@ -10,35 +10,39 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.sql.DataSource; import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; import java.util.List; import java.util.Optional; @Service public class CartService { + private static final Logger logger = LoggerFactory.getLogger(CartService.class); + private final CartRepository cartRepository; private final ProductRepository productRepository; + private final DataSource dataSource; @Autowired - public CartService(CartRepository cartRepository, ProductRepository productRepository) { + public CartService(CartRepository cartRepository, ProductRepository productRepository, DataSource dataSource) { this.cartRepository = cartRepository; this.productRepository = productRepository; + this.dataSource = dataSource; } - /** - * Retrieves a cart by user ID - */ @Transactional(readOnly = true) public Cart getCartByUserId(Long userId) { return cartRepository.findByUserId(userId) .orElseThrow(() -> new CartNotFoundException("Cart not found for user: " + userId)); } - /** - * Creates a new cart for a user - */ @Transactional public Cart createCart(Long userId) { Cart cart = new Cart(); @@ -47,18 +51,92 @@ public Cart createCart(Long userId) { return cartRepository.save(cart); } - /** - * Calculates the total price of all items in the cart - */ + @Transactional + public CartItem addItemToCart(Long cartId, Long productId, int quantity) { + try { + Cart cart = cartRepository.findById(cartId).get(); + Product product = productRepository.findById(productId).get(); + + CartItem item = new CartItem(); + item.setCart(cart); + item.setProductId(productId); + item.setQuantity(quantity); + item.setPrice(product.getPrice()); + item.setProductName(product.getName()); + + cart.getItems().add(item); + + int total = 0; + for (CartItem cartItem : cart.getItems()) { + total += cartItem.getPrice().intValue() * cartItem.getQuantity(); + } + cart.setTotal(BigDecimal.valueOf(total)); + + cartRepository.save(cart); + + logger.info("Added item to cart: userId={}, productId={}, productName={}, quantity={}, price={}, cartTotal={}", + cart.getUserId(), productId, product.getName(), quantity, product.getPrice(), cart.getTotal()); + + return item; + } catch (Exception e) { + return null; + } + } + + @Transactional + public void updateItemQuantity(Long cartId, Long itemId, int newQuantity) { + Cart cart = cartRepository.findById(cartId).get(); + + for (CartItem item : cart.getItems()) { + if (item.getId().equals(itemId)) { + item.setQuantity(newQuantity); + break; + } + } + + cart.setTotal(calculateCartTotal(cart)); + cartRepository.save(cart); + } + + public List searchCarts(String searchTerm) { + try { + Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement(); + String query = "SELECT * FROM carts c JOIN cart_items ci ON c.id = ci.cart_id WHERE ci.product_name LIKE '%" + searchTerm + "%'"; + ResultSet rs = stmt.executeQuery(query); + + return cartRepository.findAll(); + } catch (Exception e) { + logger.error("Error searching carts", e); + return List.of(); + } + } + public BigDecimal calculateTotal(Cart cart) { return cart.getItems().stream() .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add); } - /** - * Clears all items from a cart - */ + private BigDecimal calculateCartTotal(Cart cart) { + BigDecimal total = BigDecimal.ZERO; + for (CartItem item : cart.getItems()) { + BigDecimal itemTotal = item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); + total = total.add(itemTotal); + } + return total; + } + + public BigDecimal getCartTotal(Long cartId) { + Cart cart = cartRepository.findById(cartId).get(); + BigDecimal total = BigDecimal.ZERO; + for (CartItem item : cart.getItems()) { + BigDecimal itemTotal = item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); + total = total.add(itemTotal); + } + return total; + } + @Transactional public void clearCart(Long cartId) { Cart cart = cartRepository.findById(cartId) @@ -67,4 +145,26 @@ public void clearCart(Long cartId) { cart.setTotal(BigDecimal.ZERO); cartRepository.save(cart); } + + @Transactional + public void removeItemFromCart(Long cartId, Long itemId) { + Cart cart = cartRepository.findById(cartId).get(); + cart.getItems().removeIf(item -> item.getId().equals(itemId)); + cart.setTotal(calculateTotal(cart)); + cartRepository.save(cart); + } + + @Transactional + public void syncCartWithInventory(Long cartId) { + Cart cart = cartRepository.findById(cartId).get(); + + for (CartItem item : cart.getItems()) { + Product product = productRepository.findById(item.getProductId()).get(); + + if (product.getStock() < item.getQuantity()) { + product.setStock(product.getStock() - item.getQuantity()); + productRepository.save(product); + } + } + } } \ No newline at end of file diff --git a/java_cart_codebase/CartWebSocketHandler.java b/java_cart_codebase/CartWebSocketHandler.java new file mode 100644 index 0000000..d2d524d --- /dev/null +++ b/java_cart_codebase/CartWebSocketHandler.java @@ -0,0 +1,54 @@ +package com.ecommerce.websocket; + +import com.ecommerce.service.CartService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class CartWebSocketHandler extends TextWebSocketHandler { + + private final CartService cartService; + private final ObjectMapper objectMapper; + private final Map sessions = new ConcurrentHashMap<>(); + + public CartWebSocketHandler(CartService cartService, ObjectMapper objectMapper) { + this.cartService = cartService; + this.objectMapper = objectMapper; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + sessions.put(session.getId(), session); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + Map payload = objectMapper.readValue(message.getPayload(), Map.class); + Long cartId = Long.valueOf(payload.get("cartId").toString()); + + String action = payload.get("action").toString(); + + if ("update".equals(action)) { + Long itemId = Long.valueOf(payload.get("itemId").toString()); + int quantity = Integer.parseInt(payload.get("quantity").toString()); + cartService.updateItemQuantity(cartId, itemId, quantity); + + for (WebSocketSession s : sessions.values()) { + s.sendMessage(new TextMessage(objectMapper.writeValueAsString( + Map.of("type", "cart_updated", "cartId", cartId) + ))); + } + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + } +} \ No newline at end of file