Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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" ]
44 changes: 44 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
Expand Down
69 changes: 68 additions & 1 deletion src/main/java/com/podzilla/order/controller/OrderController.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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")
Expand All @@ -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;
Expand Down Expand Up @@ -88,4 +96,63 @@ public ResponseEntity<Void> 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<Optional<Order>> getOrderByUserId(
@PathVariable final long userId) {
Optional<Order> 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<Order> 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<Order> 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<Order> checkoutOrder(@PathVariable final long id) {
Order order = orderService.checkoutOrder(id);
LOGGER.info("Order with ID: {} checked out", id);
return ResponseEntity.ok(order);
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/podzilla/order/exception/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<ErrorResponse> handleAccessDeniedException(
final AccessDeniedException exception) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.FORBIDDEN));
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthenticationException(
final AuthenticationException exception) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.UNAUTHORIZED));
}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFoundException(
final NotFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.NOT_FOUND));
}

@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
final ValidationException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.BAD_REQUEST));
}

@ExceptionHandler(InvalidActionException.class)
public ResponseEntity<ErrorResponse> handleInvalidActionException(
final InvalidActionException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.BAD_REQUEST));
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(
final Exception exception) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(exception.getMessage(),
HttpStatus.INTERNAL_SERVER_ERROR));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.podzilla.order.exception;

public class InvalidActionException extends RuntimeException {
public InvalidActionException(final String message) {
super("Invalid action: " + message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.podzilla.order.exception;

public class NotFoundException extends RuntimeException {
public NotFoundException(final String message) {
super("Not Found: " + message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.podzilla.order.exception;

public class ValidationException extends RuntimeException {
public ValidationException(final String message) {
super("Validation error: " + message);
}
}
6 changes: 3 additions & 3 deletions src/main/java/com/podzilla/order/model/OrderStatus.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.podzilla.order.model;

public enum OrderStatus {
PENDING,
CONFIRMED,
PLACED,
CANCELLED,
SHIPPED,
DELIVERED,
CANCELLED
FAILED
}

Original file line number Diff line number Diff line change
Expand Up @@ -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<Order, Long> {
Optional<Order> findByUserId(Long id);
Comment thread
NourAlPha marked this conversation as resolved.
}
53 changes: 52 additions & 1 deletion src/main/java/com/podzilla/order/service/OrderService.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -61,4 +63,53 @@ public void deleteOrder(final long id) {
throw new RuntimeException("Order not found with id: " + id);
}
}

public Optional<Order> getOrderByUserId(final long userId) {
log.info("Fetching order with user ID: {}", userId);

Optional<Order> 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<Order> 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<Order> 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);
}
}
}
Loading