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; }