From 1abdd28e0e7eaf2c3de90fa57f23e5cbc420bd61 Mon Sep 17 00:00:00 2001
From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com>
Date: Sat, 3 May 2025 00:36:06 +0300
Subject: [PATCH 1/2] feat : adding the crud operations in all layers
---
.idea/.gitignore | 8 ++
.idea/compiler.xml | 26 ++++
.idea/encodings.xml | 7 ++
.idea/jarRepositories.xml | 20 +++
.idea/misc.xml | 14 +++
.idea/vcs.xml | 6 +
pom.xml | 115 ++++++++++++++++++
.../java/cart/controller/CartController.java | 111 +++++++++++++++++
src/main/java/cart/model/Cart.java | 59 +++++++++
src/main/java/cart/model/CartItem.java | 39 ++++++
.../java/cart/repository/CartRepository.java | 12 ++
src/main/java/cart/service/CartService.java | 93 ++++++++++++++
12 files changed, 510 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/compiler.xml
create mode 100644 .idea/encodings.xml
create mode 100644 .idea/jarRepositories.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/vcs.xml
create mode 100644 pom.xml
create mode 100644 src/main/java/cart/controller/CartController.java
create mode 100644 src/main/java/cart/model/Cart.java
create mode 100644 src/main/java/cart/model/CartItem.java
create mode 100644 src/main/java/cart/repository/CartRepository.java
create mode 100644 src/main/java/cart/service/CartService.java
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..3f44e05
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..712ab9d
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..0e6e319
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..67ec612
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,115 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.5
+
+
+
+ com.podzilla
+ cart
+ 0.0.1-SNAPSHOT
+ cart
+ This is the cart service for Podzilla
+
+
+ 23
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.2
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.8.5
+
+
+
+
+ net.logstash.logback
+ logstash-logback-encoder
+ 7.4
+
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java
new file mode 100644
index 0000000..9b13d51
--- /dev/null
+++ b/src/main/java/cart/controller/CartController.java
@@ -0,0 +1,111 @@
+package cart.controller;
+
+
+
+import cart.model.Cart;
+import cart.service.CartService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import cart.model.CartItem;
+import io.swagger.v3.oas.annotations.media.Content;
+
+@RestController
+@RequestMapping("/api/cart")
+@RequiredArgsConstructor
+@Tag(name = "Cart Controller", description = "Handles cart operations like add, update, remove items and manage cart")
+public class CartController {
+
+ private final CartService cartService;
+
+ @Operation(summary = "Create a new cart for a customer or return existing one")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Cart created or retrieved successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid customer ID provided", content = @Content),
+ @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content)
+ })
+ @PostMapping("/create/{customerId}")
+ public ResponseEntity createCart(@PathVariable("customerId") String customerId) {
+ Cart cart = cartService.createCart(customerId);
+ return ResponseEntity.ok(cart);
+ }
+
+ @Operation(summary = "Get cart by customer ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Cart retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found for this customer")
+ })
+ @GetMapping("/customer/{customerId}")
+ public ResponseEntity getCartByCustomerId(@PathVariable("customerId") String customerId) {
+ Cart cart = cartService.getCartByCustomerId(customerId);
+ return ResponseEntity.ok(cart);
+ }
+
+ @Operation(summary = "Delete cart by customer ID")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Cart deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found")
+ })
+ @DeleteMapping("/customer/{customerId}")
+ public ResponseEntity deleteCart(@PathVariable("customerId") String customerId) {
+ cartService.deleteCartByCustomerId(customerId);
+ return ResponseEntity.noContent().build();
+ }
+
+ @Operation(summary = "Add an item to the cart or update its quantity if already exists")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Item added or updated successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid item data provided"),
+ @ApiResponse(responseCode = "404", description = "Cart not found for this customer")
+ })
+ @PostMapping("/{customerId}/items")
+ public ResponseEntity addItemToCart(
+ @PathVariable("customerId") String customerId,
+ @RequestBody CartItem cartItem) {
+ Cart updatedCart = cartService.addItemToCart(customerId, cartItem);
+ return ResponseEntity.ok(updatedCart);
+ }
+
+ @Operation(summary = "Update quantity of an existing item in the cart")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Quantity updated successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid quantity value"),
+ @ApiResponse(responseCode = "404", description = "Cart or item not found")
+ })
+ @PatchMapping("/{customerId}/items/{productId}")
+ public ResponseEntity updateItemQuantity(
+ @PathVariable("customerId") String customerId,
+ @PathVariable("productId") String productId,
+ @RequestParam int quantity) {
+ Cart updatedCart = cartService.updateItemQuantity(customerId, productId, quantity);
+ return ResponseEntity.ok(updatedCart);
+ }
+
+ @Operation(summary = "Remove an item from the cart")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Item removed successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart or item not found")
+ })
+ @DeleteMapping("/{customerId}/items/{productId}")
+ public ResponseEntity removeItemFromCart(
+ @PathVariable("customerId") String customerId,
+ @PathVariable("productId") String productId) {
+ Cart updatedCart = cartService.removeItemFromCart(customerId, productId);
+ return ResponseEntity.ok(updatedCart);
+ }
+
+ @Operation(summary = "Clear all items from the cart")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Cart cleared successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart not found")
+ })
+ @DeleteMapping("/{customerId}/clear")
+ public ResponseEntity clearCart(@PathVariable("customerId") String customerId) {
+ cartService.clearCart(customerId);
+ return ResponseEntity.noContent().build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cart/model/Cart.java b/src/main/java/cart/model/Cart.java
new file mode 100644
index 0000000..a34754d
--- /dev/null
+++ b/src/main/java/cart/model/Cart.java
@@ -0,0 +1,59 @@
+package cart.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Document(collection = "carts")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Cart {
+
+
+ @Id
+ private String id;
+
+ private String customerId;
+
+ private List items = new ArrayList<>();
+
+ @Override
+ public String toString() {
+ return "Cart{"
+ + "id='" + id + '\''
+ + ", customerId='" + customerId + '\''
+ + ", items=" + items
+ + '}';
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public String getCustomerId() {
+ return customerId;
+ }
+
+ public void setCustomerId(final String customerId) {
+ this.customerId = customerId;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(final List items) {
+ this.items = items;
+ }
+}
+
diff --git a/src/main/java/cart/model/CartItem.java b/src/main/java/cart/model/CartItem.java
new file mode 100644
index 0000000..92e77dc
--- /dev/null
+++ b/src/main/java/cart/model/CartItem.java
@@ -0,0 +1,39 @@
+package cart.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CartItem {
+
+ private String productId;
+
+ private int quantity;
+
+ @Override
+ public String toString() {
+ return "CartItem{"
+ + "productId='" + productId + '\''
+ + ", quantity=" + quantity
+ + '}';
+ }
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public void setProductId(final String productId) {
+ this.productId = productId;
+ }
+
+ public int getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(int quantity) {
+ this.quantity = quantity;
+ }
+}
diff --git a/src/main/java/cart/repository/CartRepository.java b/src/main/java/cart/repository/CartRepository.java
new file mode 100644
index 0000000..d637b49
--- /dev/null
+++ b/src/main/java/cart/repository/CartRepository.java
@@ -0,0 +1,12 @@
+package cart.repository;
+
+import cart.model.Cart;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface CartRepository extends MongoRepository {
+ Optional findByCustomerId(final String customerId);
+}
\ No newline at end of file
diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java
new file mode 100644
index 0000000..4995a1d
--- /dev/null
+++ b/src/main/java/cart/service/CartService.java
@@ -0,0 +1,93 @@
+package cart.service;
+
+import cart.model.Cart;
+import cart.model.CartItem;
+import cart.repository.CartRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class CartService {
+
+ private final CartRepository cartRepository;
+
+
+ public Cart createCart(final String customerId) {
+ return cartRepository.findByCustomerId(customerId)
+ .orElseGet(() -> {
+ Cart newCart = new Cart(UUID.randomUUID().toString(), customerId, new ArrayList<>());
+ return cartRepository.save(newCart);
+ });
+ }
+
+ public Cart addItemToCart(final String customerId, final CartItem newItem) {
+ Cart cart = getCartByCustomerId(customerId);
+
+ Optional existingItem = cart.getItems().stream()
+ .filter(i -> i.getProductId().equals(newItem.getProductId()))
+ .findFirst();
+
+ if (existingItem.isPresent()) {
+ existingItem.get().setQuantity(existingItem.get().getQuantity() + newItem.getQuantity());
+ } else {
+ cart.getItems().add(newItem);
+ }
+
+ return cartRepository.save(cart);
+ }
+
+
+ public Cart updateItemQuantity(final String customerId, final String productId, final int quantity) {
+ Cart cart = getCartByCustomerId(customerId);
+
+ Optional existingItemOpt = cart.getItems().stream()
+ .filter(i -> i.getProductId().equals(productId))
+ .findFirst();
+
+ if (existingItemOpt.isEmpty()) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found in cart");
+ }
+
+ CartItem item = existingItemOpt.get();
+
+ if (quantity <= 0) {
+ cart.getItems().remove(item);
+ } else {
+ item.setQuantity(quantity);
+ }
+
+ return cartRepository.save(cart);
+
+ }
+
+
+ public Cart removeItemFromCart(final String customerId,final String productId) {
+ Cart cart = getCartByCustomerId(customerId);
+ cart.getItems().removeIf(i -> i.getProductId().equals(productId));
+ return cartRepository.save(cart);
+ }
+
+ public void deleteCartByCustomerId(final String customerId) {
+ cartRepository.findByCustomerId(customerId).ifPresent(cartRepository::delete);
+ }
+
+ public Cart getCartByCustomerId(final String customerId) {
+ return cartRepository.findByCustomerId(customerId)
+ .orElseThrow(() -> new NoSuchElementException("Cart not found"));
+ }
+
+ public void clearCart(final String customerId) {
+ Cart cart = getCartByCustomerId(customerId);
+ cart.getItems().clear();
+ cartRepository.save(cart);
+ }
+
+}
From f56c1ae1c2ff94ee0659691059bcb0dcb6c58fc6 Mon Sep 17 00:00:00 2001
From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com>
Date: Sun, 18 May 2025 22:18:47 +0300
Subject: [PATCH 2/2] refactore : only admin can access promocode api
---
.idea/compiler.xml | 2 +-
.../java/com/podzilla/cart/controller/PromoCodeController.java | 2 ++
src/main/java/com/podzilla/cart/model/Cart.java | 3 ++-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 736c668..3f44e05 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/src/main/java/com/podzilla/cart/controller/PromoCodeController.java b/src/main/java/com/podzilla/cart/controller/PromoCodeController.java
index 1ff1bab..969545e 100644
--- a/src/main/java/com/podzilla/cart/controller/PromoCodeController.java
+++ b/src/main/java/com/podzilla/cart/controller/PromoCodeController.java
@@ -1,5 +1,6 @@
package com.podzilla.cart.controller;
+import com.podzilla.auth.annotations.AllowedRoles;
import com.podzilla.cart.model.PromoCode;
import com.podzilla.cart.service.PromoCodeService;
import io.swagger.v3.oas.annotations.Operation;
@@ -22,6 +23,7 @@
@RestController
@RequestMapping("/admin/promocodes")
+@AllowedRoles({"ROLE_ADMIN"})
@RequiredArgsConstructor
@Tag(name = "PromoCode Admin", description = "Manage promotional codes (Requires Admin Role)")
@Slf4j
diff --git a/src/main/java/com/podzilla/cart/model/Cart.java b/src/main/java/com/podzilla/cart/model/Cart.java
index ad33344..19c681b 100644
--- a/src/main/java/com/podzilla/cart/model/Cart.java
+++ b/src/main/java/com/podzilla/cart/model/Cart.java
@@ -32,9 +32,10 @@ public class Cart {
private String appliedPromoCode;
private BigDecimal subTotal = BigDecimal.ZERO;
+
private BigDecimal discountAmount = BigDecimal.ZERO;
- private BigDecimal totalPrice = BigDecimal.ZERO;
+ private BigDecimal totalPrice = BigDecimal.ZERO;
}