Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1abdd28
feat : adding the crud operations in all layers
Aelmeky May 2, 2025
16a78e3
adding clear cart method
Aelmeky May 2, 2025
39fce86
adding docker file
Aelmeky May 2, 2025
075828c
adding git ignore
Aelmeky May 2, 2025
58841ee
adding the 3 endpoints archive and unarchive and checkout
Aelmeky May 3, 2025
26c31a8
fixing comments and logger config
Aelmeky May 3, 2025
785d685
Merge branch 'dev' into meky/crud-operations
Aelmeky May 3, 2025
4a54a18
fixing lint errors
Aelmeky May 4, 2025
926d0f9
Merge branch 'meky/crud-operations' of https://github.com/Podzilla/ca…
Aelmeky May 4, 2025
95a8640
fixing lint errors
Aelmeky May 4, 2025
838c209
fixing lint errors
Aelmeky May 4, 2025
e7ff631
refactore : adding logger to controller and resloving comments
Aelmeky May 7, 2025
bac8a8f
feat : adding logger to the cart service
Aelmeky May 7, 2025
2f3e9a3
fix : fixing sytnax error
Aelmeky May 7, 2025
2bd01ab
adding logs to the gitignire and fixing lint errors
Aelmeky May 7, 2025
79640f1
tests : adding unit tests for the cart service
Aelmeky May 8, 2025
f5822f6
adding global hanlder
Aelmeky May 8, 2025
e09f575
fix : fixing lint errors
Aelmeky May 8, 2025
1e78cd7
tests: fixing tests after adding the handler
Aelmeky May 8, 2025
84a5d71
Delete logs/app.log
Aelmeky May 8, 2025
5346850
feat : adding command design pattern and singlton and updating tests
Aelmeky May 11, 2025
bebc827
reafctor : fixing lint error
Aelmeky May 11, 2025
efb3af5
feat: adding promocode and checkout
Aelmeky May 13, 2025
0285b6d
fix & test: fixing cart service and the cart service tests
Aelmeky May 13, 2025
5cd549e
tests: adding unit tests for the promocode service:
Aelmeky May 13, 2025
cb666c4
Merge branch 'dev' of https://github.com/Podzilla/cart into dev
Aelmeky May 13, 2025
78367d8
Merge branch 'dev' into meky/crud-operations
Aelmeky May 13, 2025
6c207a5
fix: fixing lint and syntax erros
Aelmeky May 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
<version>2.8.5</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<!-- Logging -->
<dependency>
<groupId>net.logstash.logback</groupId>
Expand Down Expand Up @@ -87,7 +92,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
<!-- Mockito JUnit 5 Extension -->
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/cart/config/RabbitMQConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cart.config;

//import org.springframework.amqp.core.Binding;
//import org.springframework.amqp.core.BindingBuilder;
//import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

@Value("${rabbitmq.exchange.name}")
private String exchangeName;

@Value("${rabbitmq.routing.key.checkout}")
private String checkoutRoutingKey;

// Define the exchange (e.g., a Topic Exchange)
@Bean
TopicExchange cartEventsExchange() {
return new TopicExchange(exchangeName);
}

// Note: The Cart Service *produces* messages. The *consumer* (Order Service)
// would typically define the Queue and the Binding. However, defining the
// exchange here is good practice for the producer.
// If the Cart service also needed to *consume* events (e.g., order confirmations),
// you would define Queues and Bindings here as well.

/* Example Consumer setup (would be in Order Service):
@Value("${rabbitmq.queue.name.order}") // e.g., q.order.checkout
private String orderQueueName;

@Bean
Queue orderQueue() {
return new Queue(orderQueueName, true); // durable=true
}

@Bean
Binding orderBinding(Queue orderQueue, TopicExchange cartEventsExchange) {
return BindingBuilder.bind(orderQueue).to(cartEventsExchange).with(checkoutRoutingKey);
}
*/

// You might also need a MessageConverter bean (e.g., Jackson2JsonMessageConverter)
// if you haven't configured one globally, to ensure your CheckoutEvent object
// is serialized correctly (usually auto-configured by Spring Boot).
}
41 changes: 41 additions & 0 deletions src/main/java/cart/controller/CartController.java
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,45 @@ public ResponseEntity<Cart> checkoutCart(
+ "communicating with Order Service");
}
}

@Operation(summary = "Apply a promo code to the cart")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Promo code applied successfully"),
@ApiResponse(responseCode = "400",
description = "Invalid or expired promo code"),
@ApiResponse(responseCode = "404",
description = "Active cart not found")
})
@PostMapping("/{customerId}/promo/{promoCode}")
public ResponseEntity<Cart> applyPromoCode(
@PathVariable("customerId") final String customerId,
@PathVariable("promoCode") final String promoCode) {
log.debug("Entering applyPromoCode endpoint with "
+ "customerId: {}, promoCode: {}",
customerId, promoCode);
Cart updatedCart = cartService.applyPromoCode(
customerId, promoCode);
log.debug("Promo code applied, updated cart: "
+ "{}", updatedCart);
return ResponseEntity.ok(updatedCart);
}

@Operation(summary = "Remove the applied promo code from the cart")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Promo code removed successfully"),
@ApiResponse(responseCode = "404",
description = "Active cart not found")
})
@DeleteMapping("/{customerId}/promo")
public ResponseEntity<Cart> removePromoCode(
@PathVariable("customerId") final String customerId) {
log.debug("Entering removePromoCode "
+ "endpoint with customerId: {}", customerId);
Cart updatedCart = cartService.removePromoCode(customerId);
log.debug("Promo code removed (if any),"
+ " updated cart: {}", updatedCart);
return ResponseEntity.ok(updatedCart);
}
}
72 changes: 72 additions & 0 deletions src/main/java/cart/controller/PromoCodeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cart.controller;

import cart.model.PromoCode;
import cart.service.PromoCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestBody;


import java.util.List;

@RestController
@RequestMapping("/api/admin/promocodes")
@RequiredArgsConstructor
@Tag(name = "PromoCode Admin", description = "Manage promotional codes (Requires Admin Role)")
@Slf4j
public class PromoCodeController {

private final PromoCodeService promoCodeService;

@Operation(summary = "Create a Promo Code")
@PostMapping
public ResponseEntity<PromoCode> createOrUpdatePromoCode(
@Valid @RequestBody final PromoCode promoCode) {
log.info("Admin request to create promo code: {}", promoCode.getCode());
PromoCode savedPromoCode = promoCodeService.createOrUpdatePromoCode(promoCode);
return ResponseEntity.ok(savedPromoCode);
}

@Operation(summary = "Get all Promo Codes")
@GetMapping
public ResponseEntity<List<PromoCode>> getAllPromoCodes() {
log.info("Admin request to get all promo codes");
return ResponseEntity.ok(promoCodeService.findAll());
}

@Operation(summary = "Get a specific Promo Code by code")
@GetMapping("/{code}")
public ResponseEntity<PromoCode> getPromoCodeByCode(
@PathVariable final String code) {
log.info("Admin request to get promo code: {}", code);
return promoCodeService.findByCode(code)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

@Operation(summary = "Delete a Promo Code by code")
@DeleteMapping("/{code}")
public ResponseEntity<Void> deletePromoCode(
@PathVariable final String code) {
log.info("Admin request to delete promo code: {}", code);
try {
promoCodeService.deletePromoCode(code);
return ResponseEntity.noContent().build();
} catch (Exception e) {
log.error("Error deleting promo code", code, e.getMessage());
return ResponseEntity.status(
HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/cart/model/Cart.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -28,5 +29,12 @@ public class Cart {

private boolean archived = false;

private String appliedPromoCode;

private BigDecimal subTotal = BigDecimal.ZERO;
private BigDecimal discountAmount = BigDecimal.ZERO;
private BigDecimal totalPrice = BigDecimal.ZERO;


}

13 changes: 13 additions & 0 deletions src/main/java/cart/model/CartItem.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package cart.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@NoArgsConstructor
@AllArgsConstructor
Expand All @@ -13,6 +17,15 @@ public class CartItem {
@NotBlank
private String productId;

@NotNull
@PositiveOrZero
private int quantity;

@NotNull
@PositiveOrZero
private BigDecimal unitPrice;

public BigDecimal getItemTotal() {
return unitPrice.multiply(BigDecimal.valueOf(quantity));
}
}
11 changes: 7 additions & 4 deletions src/main/java/cart/model/OrderRequest.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package cart.model;


import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderRequest {

@NotBlank
private String eventId;
private String customerId;

private String cartId;
private List<CartItem> items;
private BigDecimal subTotal;
private BigDecimal discountAmount;
private BigDecimal totalPrice;
private String appliedPromoCode;

}
49 changes: 49 additions & 0 deletions src/main/java/cart/model/PromoCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cart.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import java.math.BigDecimal;
import java.time.Instant;

@Document(collection = "promo_codes")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PromoCode {

public enum DiscountType {
PERCENTAGE,
FIXED_AMOUNT
}

@Id
private String id;

@NotBlank
@Indexed(unique = true)
private String code;

private String description;

@NotNull
private DiscountType discountType;

@NotNull
@Positive
private BigDecimal discountValue;

private boolean active = true;

private Instant expiryDate;

private BigDecimal minimumPurchaseAmount;

}
13 changes: 13 additions & 0 deletions src/main/java/cart/repository/PromoCodeRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cart.repository;


import cart.model.PromoCode;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface PromoCodeRepository extends MongoRepository<PromoCode, String> {
Optional<PromoCode> findByCode(String code);
}
Loading