feat: add GET /api/admin/orders and PATCH /api/admin/orders/{id}/status

- AdminOrderResponse DTO: extends OrderResponse with email (from User
  relation) and letterText fields, exposing the full order for admin review
- UpdateStatusRequest DTO: single "status" field validated against all
  six OrderStatus values (pending_payment|paid|lookup_started|sent|
  delivered|failed) with Swedish error messages
- OrderService.getAllOrders(): delegates to OrderRepository
  .findAllByOrderByCreatedAtDesc() which uses @EntityGraph to eagerly
  fetch the user relationship in a single query
- OrderService.updateOrderStatus(orderId, statusString): looks up order,
  converts status string to OrderStatus enum via case-insensitive
  valueOf(), saves updated entity
- AdminController /api/admin:
  GET  /orders              → list all orders with user email (admin only)
  PATCH /orders/{id}/status → update order status (admin only)
- toAdminResponse() mapper safely handles null user (empty email fallback)
This commit is contained in:
Joakim Mörling 2026-05-15 12:14:53 +02:00
parent 8217b9c038
commit 76028fa94d
4 changed files with 98 additions and 0 deletions

View file

@ -0,0 +1,56 @@
package se.bilhalsning.controller;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import se.bilhalsning.dto.AdminOrderResponse;
import se.bilhalsning.dto.UpdateStatusRequest;
import se.bilhalsning.entity.Order;
import se.bilhalsning.service.OrderService;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/admin")
@RequiredArgsConstructor
public class AdminController {
private final OrderService orderService;
@GetMapping("/orders")
public ResponseEntity<List<AdminOrderResponse>> listAllOrders() {
List<AdminOrderResponse> orders = orderService.getAllOrders().stream()
.map(this::toAdminResponse)
.toList();
return ResponseEntity.ok(orders);
}
@PatchMapping("/orders/{id}/status")
public ResponseEntity<AdminOrderResponse> updateStatus(
@PathVariable UUID id,
@Valid @RequestBody UpdateStatusRequest request) {
Order order = orderService.updateOrderStatus(id, request.status());
return ResponseEntity.ok(toAdminResponse(order));
}
private AdminOrderResponse toAdminResponse(Order order) {
String email = order.getUser() != null ? order.getUser().getEmail() : "";
return new AdminOrderResponse(
order.getId(),
email,
order.getPlate(),
order.getLetterText(),
order.getStatus().getValue(),
order.getTrackingId(),
order.getAmountPaid(),
order.getCreatedAt()
);
}
}

View file

@ -0,0 +1,16 @@
package se.bilhalsning.dto;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.UUID;
public record AdminOrderResponse(
UUID id,
String email,
String plate,
String letterText,
String status,
String trackingId,
BigDecimal amountPaid,
Instant createdAt
) {}

View file

@ -0,0 +1,13 @@
package se.bilhalsning.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
public record UpdateStatusRequest(
@NotBlank(message = "Status krävs")
@Pattern(
regexp = "pending_payment|paid|lookup_started|sent|delivered|failed",
message = "Ogiltig status"
)
String status
) {}

View file

@ -33,4 +33,17 @@ public class OrderService {
return orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
public List<Order> getAllOrders() {
return orderRepository.findAllByOrderByCreatedAtDesc();
}
public Order updateOrderStatus(UUID orderId, String statusString) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
OrderStatus newStatus = OrderStatus.valueOf(statusString.toUpperCase());
order.setStatus(newStatus);
return orderRepository.save(order);
}
}