diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c2a45ba
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM openjdk:25-ea-4-jdk-oraclelinux9
+
+WORKDIR /app
+
+COPY target/order-0.0.1-SNAPSHOT.jar /app/order-0.0.1-SNAPSHOT.jar
+
+ENTRYPOINT [ "java", "-jar", "/app/order-0.0.1-SNAPSHOT.jar" ]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..c126715
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,44 @@
+services:
+ backend:
+ image: openjdk:25-ea-4-jdk-oraclelinux9
+ container_name: order
+ ports:
+ - "8080:8080"
+ depends_on:
+ - order_db
+ environment:
+ - SPRING_DATASOURCE_URL=jdbc:postgresql://order_db:5432/orderDB
+ volumes:
+ - ./target:/app
+ - ./logs:/logs
+ command: [ "java", "-jar", "/app/order-0.0.1-SNAPSHOT.jar" ]
+
+ order_db:
+ image: postgres:14.17
+ container_name: order_db
+ environment:
+ POSTGRES_PASSWORD: 1234
+ POSTGRES_USER: postgres
+ POSTGRES_DB: orderDB
+ ports:
+ - "5432:5432"
+
+ 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 f342395..a7ebd93 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,20 @@
springdoc-openapi-starter-webmvc-ui
2.8.5
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.8.5
+
+
+ net.logstash.logback
+ logstash-logback-encoder
+ 7.4
+
org.springframework.boot
spring-boot-starter-data-jpa
diff --git a/src/main/java/com/podzilla/order/controller/OrderController.java b/src/main/java/com/podzilla/order/controller/OrderController.java
index 6170b14..dff1524 100644
--- a/src/main/java/com/podzilla/order/controller/OrderController.java
+++ b/src/main/java/com/podzilla/order/controller/OrderController.java
@@ -1,6 +1,7 @@
package com.podzilla.order.controller;
import com.podzilla.order.model.Order;
+import com.podzilla.order.model.OrderStatus;
import com.podzilla.order.service.OrderService;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,12 +14,16 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.DeleteMapping;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Optional;
+
@RestController
@RequestMapping("/orders")
@Tag(name = "Order API", description = "Order management operations")
@@ -28,6 +33,9 @@ public class OrderController {
private static final Logger LOGGER =
LoggerFactory.getLogger(OrderController.class);
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(OrderController.class);
+
@Autowired
public OrderController(final OrderService orderService) {
this.orderService = orderService;
@@ -88,4 +96,63 @@ public ResponseEntity deleteOrder(@PathVariable final long id) {
orderService.deleteOrder(id);
return ResponseEntity.noContent().build();
}
+
+ @GetMapping("/user/{userId}")
+ @Operation(
+ summary = "Get order by user ID",
+ description = "Fetches an order based on the provided user ID"
+ )
+ @ApiResponse(
+ responseCode = "200", description = "Order found"
+ )
+ public ResponseEntity> getOrderByUserId(
+ @PathVariable final long userId) {
+ Optional order = orderService.getOrderByUserId(userId);
+ LOGGER.info("Order found for user ID: {}", userId);
+ return ResponseEntity.ok(order);
+ }
+
+ @PutMapping("/cancel/{id}")
+ @Operation(
+ summary = "Cancel order",
+ description = "Cancels an order based on the provided order ID"
+ )
+ @ApiResponse(
+ responseCode = "200", description = "Order cancelled"
+ )
+ public ResponseEntity cancelOrder(@PathVariable final long id) {
+ Order order = orderService.cancelOrder(id);
+ LOGGER.info("Order with ID: {} cancelled", id);
+ return ResponseEntity.ok(order);
+ }
+
+ @PutMapping("/status/{id}")
+ @Operation(
+ summary = "Update order status",
+ description = "Updates the status of an order based on "
+ + "the provided order ID"
+ )
+ @ApiResponse(
+ responseCode = "200", description = "Order status updated"
+ )
+ public ResponseEntity updateOrderStatus(@PathVariable final long id,
+ @RequestBody final OrderStatus status) {
+ Order order = orderService.updateOrderStatus(id, status);
+ LOGGER.info("Order status updated for ID: {}", id);
+ return ResponseEntity.ok(order);
+ }
+
+ @PostMapping("/checkout/{id}")
+ @Operation(
+ summary = "Checkout order",
+ description = "Checks out an order based on the provided order ID"
+ )
+ @ApiResponse(
+ responseCode = "200", description = "Order checked out"
+ )
+ public ResponseEntity checkoutOrder(@PathVariable final long id) {
+ Order order = orderService.checkoutOrder(id);
+ LOGGER.info("Order with ID: {} checked out", id);
+ return ResponseEntity.ok(order);
+ }
}
diff --git a/src/main/java/com/podzilla/order/exception/ErrorResponse.java b/src/main/java/com/podzilla/order/exception/ErrorResponse.java
new file mode 100644
index 0000000..c752990
--- /dev/null
+++ b/src/main/java/com/podzilla/order/exception/ErrorResponse.java
@@ -0,0 +1,19 @@
+package com.podzilla.order.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+import java.time.LocalDateTime;
+
+@Getter
+public class ErrorResponse {
+ private final String message;
+ private final HttpStatus status;
+ private final LocalDateTime timestamp;
+
+ public ErrorResponse(final String message, final HttpStatus status) {
+ this.message = message;
+ this.status = status;
+ this.timestamp = LocalDateTime.now();
+ }
+}
diff --git a/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java b/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..0f19f36
--- /dev/null
+++ b/src/main/java/com/podzilla/order/exception/GlobalExceptionHandler.java
@@ -0,0 +1,62 @@
+package com.podzilla.order.exception;
+
+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;
+
+@RestControllerAdvice
+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) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .body(new ErrorResponse(exception.getMessage(),
+ HttpStatus.NOT_FOUND));
+ }
+
+ @ExceptionHandler(ValidationException.class)
+ public ResponseEntity handleValidationException(
+ final ValidationException exception) {
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ .body(new ErrorResponse(exception.getMessage(),
+ HttpStatus.BAD_REQUEST));
+ }
+
+ @ExceptionHandler(InvalidActionException.class)
+ public ResponseEntity handleInvalidActionException(
+ final InvalidActionException exception) {
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ .body(new ErrorResponse(exception.getMessage(),
+ HttpStatus.BAD_REQUEST));
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity handleException(
+ final Exception exception) {
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(new ErrorResponse(exception.getMessage(),
+ HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+}
diff --git a/src/main/java/com/podzilla/order/exception/InvalidActionException.java b/src/main/java/com/podzilla/order/exception/InvalidActionException.java
new file mode 100644
index 0000000..89ce1d5
--- /dev/null
+++ b/src/main/java/com/podzilla/order/exception/InvalidActionException.java
@@ -0,0 +1,7 @@
+package com.podzilla.order.exception;
+
+public class InvalidActionException extends RuntimeException {
+ public InvalidActionException(final String message) {
+ super("Invalid action: " + message);
+ }
+}
diff --git a/src/main/java/com/podzilla/order/exception/NotFoundException.java b/src/main/java/com/podzilla/order/exception/NotFoundException.java
new file mode 100644
index 0000000..7a2c0da
--- /dev/null
+++ b/src/main/java/com/podzilla/order/exception/NotFoundException.java
@@ -0,0 +1,7 @@
+package com.podzilla.order.exception;
+
+public class NotFoundException extends RuntimeException {
+ public NotFoundException(final String message) {
+ super("Not Found: " + message);
+ }
+}
diff --git a/src/main/java/com/podzilla/order/exception/ValidationException.java b/src/main/java/com/podzilla/order/exception/ValidationException.java
new file mode 100644
index 0000000..f23efb2
--- /dev/null
+++ b/src/main/java/com/podzilla/order/exception/ValidationException.java
@@ -0,0 +1,7 @@
+package com.podzilla.order.exception;
+
+public class ValidationException extends RuntimeException {
+ public ValidationException(final String message) {
+ super("Validation error: " + message);
+ }
+}
diff --git a/src/main/java/com/podzilla/order/model/OrderStatus.java b/src/main/java/com/podzilla/order/model/OrderStatus.java
index 9dac508..07de71e 100644
--- a/src/main/java/com/podzilla/order/model/OrderStatus.java
+++ b/src/main/java/com/podzilla/order/model/OrderStatus.java
@@ -1,10 +1,10 @@
package com.podzilla.order.model;
public enum OrderStatus {
- PENDING,
- CONFIRMED,
+ PLACED,
+ CANCELLED,
SHIPPED,
DELIVERED,
- CANCELLED
+ FAILED
}
diff --git a/src/main/java/com/podzilla/order/repository/OrderRepository.java b/src/main/java/com/podzilla/order/repository/OrderRepository.java
index d9112fc..fd8f9a2 100644
--- a/src/main/java/com/podzilla/order/repository/OrderRepository.java
+++ b/src/main/java/com/podzilla/order/repository/OrderRepository.java
@@ -4,6 +4,9 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
+import java.util.Optional;
+
@Repository
public interface OrderRepository extends JpaRepository {
+ Optional findByUserId(Long id);
}
diff --git a/src/main/java/com/podzilla/order/service/OrderService.java b/src/main/java/com/podzilla/order/service/OrderService.java
index e1e27d8..c520689 100644
--- a/src/main/java/com/podzilla/order/service/OrderService.java
+++ b/src/main/java/com/podzilla/order/service/OrderService.java
@@ -1,16 +1,18 @@
package com.podzilla.order.service;
+import com.podzilla.order.exception.NotFoundException;
import com.podzilla.order.model.Order;
+import com.podzilla.order.model.OrderStatus;
import com.podzilla.order.repository.OrderRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
+
@Slf4j
@Service
public class OrderService {
@@ -61,4 +63,53 @@ public void deleteOrder(final long id) {
throw new RuntimeException("Order not found with id: " + id);
}
}
+
+ public Optional getOrderByUserId(final long userId) {
+ log.info("Fetching order with user ID: {}", userId);
+
+ Optional order = orderRepository.findByUserId(userId);
+
+ checkNotFoundException(order.orElse(null),
+ "Order not found with user ID: " + userId);
+
+ return orderRepository.findByUserId(userId);
+ }
+
+
+
+ public Order cancelOrder(final long id) {
+ log.info("Cancelling order with ID: {}", id);
+
+ Optional existingOrder = orderRepository.findById(id);
+
+ checkNotFoundException(existingOrder.orElse(null),
+ "Order not found with id: " + id);
+
+ Order order = existingOrder.get();
+ order.setStatus(OrderStatus.CANCELLED);
+ order.setUpdatedAt(LocalDateTime.now());
+ return orderRepository.save(order);
+ }
+
+ public Order updateOrderStatus(final long id,
+ final OrderStatus status) {
+ log.info("Updating order status with ID: {}", id);
+
+ Optional existingOrder = orderRepository.findById(id);
+
+ checkNotFoundException(existingOrder.orElse(null),
+ "Order not found with id: " + id);
+
+ Order order = existingOrder.get();
+ order.setStatus(status);
+ order.setUpdatedAt(LocalDateTime.now());
+ return orderRepository.save(order);
+ }
+
+ private void checkNotFoundException(final Object value,
+ final String message) {
+ if (value == null) {
+ throw new NotFoundException(message);
+ }
+ }
}