diff --git a/docker-compose.yml b/docker-compose.yml index 1ff4abe..6579263 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: - order_db environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://order_db:5432/orderDB + - SPRING_DATASOURCE_USERNAME=postgres + - SPRING_DATASOURCE_PASSWORD=1234 volumes: - ./target:/app - ./logs:/logs @@ -30,25 +32,6 @@ services: - "15672:15672" - "5672:5672" environment: - RABBITMQ_DEFAULT_USER: user - RABBITMQ_DEFAULT_PASS: password + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest - loki: - image: grafana/loki:3.5.0 - container_name: loki - ports: - - "3100:3100" - command: -config.file=/etc/loki/local-config.yaml - - grafana: - image: grafana/grafana:10.4.1 - container_name: grafana - ports: - - "3000:3000" - depends_on: - - loki - environment: - - GF_SECURITY_ADMIN_USER=admin - - GF_SECURITY_ADMIN_PASSWORD=admin - volumes: - - grafana_data:/var/lib/grafana diff --git a/pom.xml b/pom.xml index a4bf087..c944f87 100644 --- a/pom.xml +++ b/pom.xml @@ -40,20 +40,15 @@ org.springframework.boot spring-boot-starter-data-jdbc - - org.springframework.cloud - spring-cloud-starter-stream-rabbit - 4.0.0 - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.8.5 - org.springframework.boot - spring-boot-starter-security + spring-boot-starter-webflux + + + + + org.springdoc springdoc-openapi-starter-webmvc-ui diff --git a/src/main/java/com/podzilla/order/controller/OrderController.java b/src/main/java/com/podzilla/order/controller/OrderController.java index 1c18bb6..c0378a4 100644 --- a/src/main/java/com/podzilla/order/controller/OrderController.java +++ b/src/main/java/com/podzilla/order/controller/OrderController.java @@ -158,8 +158,6 @@ public ResponseEntity updateOrderStatus(@PathVariable final UUID id, return ResponseEntity.ok(order); } - - @GetMapping("/trackOrder/{id}") @Operation( summary = "Track order", diff --git a/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java b/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java index 0f19f36..2b8e2d7 100644 --- a/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java @@ -2,8 +2,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.AuthenticationException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @@ -12,22 +10,6 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException( - final AccessDeniedException exception) { - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(new ErrorResponse(exception.getMessage(), - HttpStatus.FORBIDDEN)); - } - - @ExceptionHandler(AuthenticationException.class) - public ResponseEntity handleAuthenticationException( - final AuthenticationException exception) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(new ErrorResponse(exception.getMessage(), - HttpStatus.UNAUTHORIZED)); - } - @ExceptionHandler(NotFoundException.class) public ResponseEntity handleNotFoundException( final NotFoundException exception) { diff --git a/src/main/java/com/podzilla/order/messaging/OrderConsumer.java b/src/main/java/com/podzilla/order/messaging/OrderConsumer.java index 9549673..67289ef 100644 --- a/src/main/java/com/podzilla/order/messaging/OrderConsumer.java +++ b/src/main/java/com/podzilla/order/messaging/OrderConsumer.java @@ -112,16 +112,17 @@ private void handleCartCheckoutEvent( product.setPricePerUnit(orderProduct.getPricePerUnit()); return product; }).toList(); - Order order = new Order(); - order.setUserId(UUID.fromString(cartCheckedoutEvent.getCustomerId())); - order.setTotalAmount(cartCheckedoutEvent.getTotalAmount()); - order.setStatus(OrderStatus.PENDING); - order.setShippingAddress(address); - order.setOrderProducts(orderProducts); - order.setConfirmationType(cartCheckedoutEvent.getConfirmationType()); - order.setSignature(cartCheckedoutEvent.getSignature()); - order.setOrderLatitude(cartCheckedoutEvent.getOrderLatitude()); - order.setOrderLongitude(cartCheckedoutEvent.getOrderLongitude()); + Order order = new Order.Builder() + .userId(UUID.fromString(cartCheckedoutEvent.getCustomerId())) + .totalAmount(cartCheckedoutEvent.getTotalAmount()) + .status(OrderStatus.PENDING) + .shippingAddress(address) + .orderProducts(orderProducts) + .confirmationType(cartCheckedoutEvent.getConfirmationType()) + .signature(cartCheckedoutEvent.getSignature()) + .orderLatitude(cartCheckedoutEvent.getOrderLatitude()) + .orderLongitude(cartCheckedoutEvent.getOrderLongitude()) + .build(); orderService.createOrder(order); } @@ -130,7 +131,7 @@ private void handleOrderAssignedToCourierEvent(final OrderAssignedToCourierEvent orderAssignedToCourierEvent.getOrderId()); orderService.updateOrder( UUID.fromString(orderAssignedToCourierEvent.getOrderId()), - Order.builder() + new Order.Builder() .courierId(UUID.fromString(orderAssignedToCourierEvent.getCourierId())) .status(OrderStatus.ORDER_ASSIGNED_TO_COURIER) .build()); @@ -147,7 +148,7 @@ private void handleOrderOutForDeliveryEvent(final OrderOutForDeliveryEvent order log.info("✅ Order out for delivery for order: {}", orderOutForDeliveryEvent.getOrderId()); orderService.updateOrder( UUID.fromString(orderOutForDeliveryEvent.getOrderId()), - Order.builder() + new Order.Builder() .courierId(UUID.fromString(orderOutForDeliveryEvent.getCourierId())) .status(OrderStatus.OUT_FOR_DELIVERY) .build()); diff --git a/src/main/java/com/podzilla/order/messaging/RabbitMQConfig.java b/src/main/java/com/podzilla/order/messaging/RabbitMQConfig.java deleted file mode 100644 index 2d8892b..0000000 --- a/src/main/java/com/podzilla/order/messaging/RabbitMQConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.podzilla.order.messaging; - -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.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class RabbitMQConfig { - // Exchange - public static final String EXCHANGE = "shared_exchange"; - - // Outbound (sending to warehouse) - public static final String ORDER_ROUTING_KEY = - "order.stock_reservation_requested"; - - public static final String ORDER_PLACED_ROUTING_KEY = - "order.placed"; - - // Inbound (responses from warehouse) - public static final String STOCK_RESERVED_ROUTING_KEY = - "warehouse.stock_reserved"; - public static final String ORDER_FAILED_ROUTING_KEY = - "warehouse.order_fulfillment_failed"; - - // Queues this service will consume from (responses from warehouse) - public static final String STOCK_RESERVED_QUEUE = - "order.stock_reserved.queue"; - public static final String ORDER_FAILED_QUEUE = - "order.fulfillment_failed.queue"; - - @Bean - public Queue stockReservedQueue() { - return new Queue(STOCK_RESERVED_QUEUE); - } - - @Bean - public Queue orderFailedQueue() { - return new Queue(ORDER_FAILED_QUEUE); - } - - @Bean - public TopicExchange exchange() { - return new TopicExchange(EXCHANGE); - } - - @Bean - public Binding warehouseStockReservedBinding( - final Queue stockReservedQueue, - final TopicExchange exchange) { - return BindingBuilder - .bind(stockReservedQueue) - .to(exchange) - .with("warehouse.stock_reserved"); - } - - @Bean - public Binding warehouseFulfillmentFailedBinding( - final Queue orderFailedQueue, - final TopicExchange exchange) { - return BindingBuilder - .bind(orderFailedQueue) - .to(exchange) - .with("warehouse.order_fulfillment_failed"); - } -} diff --git a/src/main/java/com/podzilla/order/model/Order.java b/src/main/java/com/podzilla/order/model/Order.java index 11f857b..902a1b4 100644 --- a/src/main/java/com/podzilla/order/model/Order.java +++ b/src/main/java/com/podzilla/order/model/Order.java @@ -13,7 +13,6 @@ import jakarta.persistence.GenerationType; import com.podzilla.mq.events.ConfirmationType; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -30,7 +29,6 @@ @AllArgsConstructor @Getter @Setter -@Builder public class Order { @Id @@ -64,4 +62,103 @@ public class Order { @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) private List orderProducts = new ArrayList<>(); + + public static class Builder { + private UUID id; + private UUID userId; + private UUID courierId; + private BigDecimal totalAmount; + private Address shippingAddress; + private OrderStatus status; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private ConfirmationType confirmationType; + private String signature; + private double orderLatitude; + private double orderLongitude; + private List orderProducts = new ArrayList<>(); + + public Builder id(final UUID id) { + this.id = id; + return this; + } + + public Builder userId(final UUID userId) { + this.userId = userId; + return this; + } + + public Builder courierId(final UUID courierId) { + this.courierId = courierId; + return this; + } + + public Builder totalAmount(final BigDecimal totalAmount) { + this.totalAmount = totalAmount; + return this; + } + + public Builder shippingAddress(final Address shippingAddress) { + this.shippingAddress = shippingAddress; + return this; + } + + public Builder status(final OrderStatus status) { + this.status = status; + return this; + } + + public Builder createdAt(final LocalDateTime createdAt) { + this.createdAt = createdAt; + return this; + } + + public Builder updatedAt(final LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public Builder confirmationType(final ConfirmationType confirmationType) { + this.confirmationType = confirmationType; + return this; + } + + public Builder signature(final String signature) { + this.signature = signature; + return this; + } + + public Builder orderLatitude(final double orderLatitude) { + this.orderLatitude = orderLatitude; + return this; + } + + public Builder orderLongitude(final double orderLongitude) { + this.orderLongitude = orderLongitude; + return this; + } + + public Builder orderProducts(final List orderProducts) { + this.orderProducts = orderProducts; + return this; + } + + public Order build() { + Order order = new Order(); + order.setId(id); + order.setUserId(userId); + order.setCourierId(courierId); + order.setTotalAmount(totalAmount); + order.setShippingAddress(shippingAddress); + order.setStatus(status); + order.setCreatedAt(createdAt); + order.setUpdatedAt(updatedAt); + order.setConfirmationType(confirmationType); + order.setSignature(signature); + order.setOrderLatitude(orderLatitude); + order.setOrderLongitude(orderLongitude); + order.setOrderProducts(orderProducts); + return order; + } + } } diff --git a/src/main/java/com/podzilla/order/service/OrderService.java b/src/main/java/com/podzilla/order/service/OrderService.java index 8bfd0a7..6444df1 100644 --- a/src/main/java/com/podzilla/order/service/OrderService.java +++ b/src/main/java/com/podzilla/order/service/OrderService.java @@ -3,7 +3,6 @@ import com.podzilla.mq.events.OrderCancelledEvent; import com.podzilla.mq.events.OrderItem; import com.podzilla.mq.events.OrderPlacedEvent; -import com.podzilla.mq.events.OrderStockReservationRequestedEvent; import com.podzilla.mq.events.DeliveryAddress; import com.podzilla.order.dtos.LocationDTO; import com.podzilla.order.exception.NotFoundException; @@ -19,6 +18,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; +import com.podzilla.order.service.statusstrategy.OrderStatusStrategy; +import com.podzilla.order.service.statusstrategy.OrderStatusStrategyFactory; import java.time.LocalDateTime; import java.util.ArrayList; @@ -33,6 +34,7 @@ public class OrderService { private final OrderRepository orderRepository; private final OrderProducer orderProducer; private final WebClient webClient; + private final OrderStatusStrategyFactory strategyFactory; @Value("${api.gateway.url}") private String apiGatewayUrl; @@ -40,10 +42,12 @@ public class OrderService { @Autowired public OrderService(final OrderRepository orderRepository, final OrderProducer orderProducer, - final WebClient webClient) { + final WebClient webClient, + final OrderStatusStrategyFactory strategyFactory) { this.orderRepository = orderRepository; this.orderProducer = orderProducer; this.webClient = webClient; + this.strategyFactory = strategyFactory; } public Order createOrder(final Order order) { @@ -51,12 +55,16 @@ public Order createOrder(final Order order) { order.setCreatedAt(LocalDateTime.now()); order.setUpdatedAt(LocalDateTime.now()); orderRepository.save(order); - OrderStockReservationRequestedEvent stockReservationRequest = - OrderStockReservationRequestedEvent.builder() - .orderId(order.getId().toString()) - .items(getOrderItems(order)) - .build(); - orderProducer.sendStockReservationRequest(stockReservationRequest); + + OrderStatusStrategy strategy = strategyFactory.getStrategy(OrderStatus.PENDING); + strategy.handle(order); + +// OrderStockReservationRequestedEvent stockReservationRequest = +// OrderStockReservationRequestedEvent.builder() +// .orderId(order.getId().toString()) +// .items(getOrderItems(order)) +// .build(); +// orderProducer.sendStockReservationRequest(stockReservationRequest); return order; } diff --git a/src/main/java/com/podzilla/order/service/statusstrategy/CreatedOrderStrategy.java b/src/main/java/com/podzilla/order/service/statusstrategy/CreatedOrderStrategy.java new file mode 100644 index 0000000..ee1e823 --- /dev/null +++ b/src/main/java/com/podzilla/order/service/statusstrategy/CreatedOrderStrategy.java @@ -0,0 +1,50 @@ +package com.podzilla.order.service.statusstrategy; + +import com.podzilla.mq.events.OrderItem; +import com.podzilla.mq.events.OrderStockReservationRequestedEvent; +import com.podzilla.order.model.Order; +import com.podzilla.order.model.OrderProduct; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; +import com.podzilla.order.messaging.OrderProducer; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class CreatedOrderStrategy implements OrderStatusStrategy { + + private final OrderProducer orderProducer; + + @Autowired + public CreatedOrderStrategy(final OrderProducer orderProducer) { + this.orderProducer = orderProducer; + } + + @Override + public void handle(final Order order) { + System.out.println("Handling CREATED status - notify user, reserve stock"); + + OrderStockReservationRequestedEvent stockReservationRequest = + OrderStockReservationRequestedEvent.builder() + .orderId(order.getId().toString()) + .items(getOrderItems(order)) + .build(); + + orderProducer.sendStockReservationRequest(stockReservationRequest); + } + + private List getOrderItems(final Order order) { + List orderItems = new ArrayList<>(); + List orderProducts = order.getOrderProducts(); + for (OrderProduct product : orderProducts) { + OrderItem orderItem = new OrderItem(); + orderItem.setProductId(product.getId().toString()); + orderItem.setQuantity(product.getQuantity()); + orderItem.setPricePerUnit(product.getPricePerUnit()); + orderItems.add(orderItem); + } + return orderItems; + } +} + diff --git a/src/main/java/com/podzilla/order/service/statusstrategy/OrderStatusStrategy.java b/src/main/java/com/podzilla/order/service/statusstrategy/OrderStatusStrategy.java new file mode 100644 index 0000000..52ee3ab --- /dev/null +++ b/src/main/java/com/podzilla/order/service/statusstrategy/OrderStatusStrategy.java @@ -0,0 +1,7 @@ +package com.podzilla.order.service.statusstrategy; + +import com.podzilla.order.model.Order; + +public interface OrderStatusStrategy { + void handle(Order order); +} diff --git a/src/main/java/com/podzilla/order/service/statusstrategy/OrderStatusStrategyFactory.java b/src/main/java/com/podzilla/order/service/statusstrategy/OrderStatusStrategyFactory.java new file mode 100644 index 0000000..39c1de8 --- /dev/null +++ b/src/main/java/com/podzilla/order/service/statusstrategy/OrderStatusStrategyFactory.java @@ -0,0 +1,30 @@ +package com.podzilla.order.service.statusstrategy; + +import com.podzilla.order.model.OrderStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +public class OrderStatusStrategyFactory { + + private final Map strategyMap; + + @Autowired + public OrderStatusStrategyFactory(final List strategies) { + strategyMap = new HashMap<>(); + for (OrderStatusStrategy strategy : strategies) { + if (strategy instanceof CreatedOrderStrategy) { + strategyMap.put(OrderStatus.PENDING, strategy); + } + } + } + + public OrderStatusStrategy getStrategy(final OrderStatus status) { + return strategyMap.get(status); + } +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6b013d9..cde2649 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,6 @@ spring.application.name=order -api.gateway.url=http://localhost:8080 \ No newline at end of file +api.gateway.url=http://localhost:8080 +spring.rabbitmq.host=rabbitmq +spring.rabbitmq.port=5672 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0bc1010..09780f5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,5 +4,5 @@ spring: rabbitmq: host: rabbitmq # container name port: 5672 - username: user - password: password + username: guest + password: guest