Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding pagination on orders-api when we getting all orders #152

Merged
merged 5 commits into from
Jul 14, 2024
Merged
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
1 change: 1 addition & 0 deletions src/backend/services/checkout-api/src/index.ts
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ async function main() {

process.on('SIGINT', async () => {
await otel.shutdown();
await checkoutPublisher.shutdown();
logger.info("Gracefully shutting down from SIGINT (Ctrl-C)");
// some other closing procedures go here
process.exit(0);
5 changes: 5 additions & 0 deletions src/backend/services/checkout-api/src/messagging/publisher.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ export class Publisher {

public async start(): Promise<void> {
try {

await this.producer.connect()
} catch (error) {
logger.error('Error connecting the producer: ', error)
@@ -32,6 +33,10 @@ export class Publisher {
const kafka = new Kafka({
clientId: this.clientId,
brokers: this.brokers,
retry: {
initialRetryTime: 3000,
retries: 15
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkout-api not getting ready during container start while Kafka broker is loading

},
})
return kafka.producer()
}
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@

import org.jboss.resteasy.reactive.RestQuery;
import org.jurabek.restaurant.order.api.dtos.OrderDto;
import org.jurabek.restaurant.order.api.dtos.OrdersDto;
import org.jurabek.restaurant.order.api.services.OrdersService;

@Path("api/v1/orders")
@@ -39,9 +40,10 @@ public OrderDto find(@RestQuery String transactionId) {
return null;
}

@GET
public List<OrderDto> getData() {
return this.ordersService.getAll();
@GET()
public OrdersDto getAll(@RestQuery Integer offset, @RestQuery Integer limit) {
var orders = ordersService.getAll(offset, limit);
return new OrdersDto(orders, ordersService.getCount(), offset, limit);
}

@GET
Original file line number Diff line number Diff line change
@@ -13,5 +13,8 @@
public class OrderDto {
private UUID id;
private Date orderedDate;
private UUID cartId;
private UUID transactionId;
private UUID checkoutId;
private List<OrderItemDto> orderItems;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.jurabek.restaurant.order.api.dtos;

import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;

@Data
@AllArgsConstructor
public class OrdersDto {
private List<OrderDto> orders;
private Long total;
private Integer offset;
private Integer limit;
}
Original file line number Diff line number Diff line change
@@ -2,15 +2,11 @@

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import io.smallrye.common.annotation.Blocking;

import java.util.concurrent.CompletionStage;
import jakarta.transaction.Transactional;

import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.reactive.messaging.Acknowledgment;
import org.eclipse.microprofile.reactive.messaging.Incoming;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import org.jboss.logging.Logger;
import org.jurabek.restaurant.order.api.services.CheckoutService;

@@ -19,25 +15,26 @@ public class UserCheckoutEventHandler {

private static final Logger log = Logger.getLogger(UserCheckoutEventHandler.class);

private final CheckoutService checkout;
private final CheckoutService checkoutService;

@Inject
public UserCheckoutEventHandler(CheckoutService checkout) {
this.checkout = checkout;
public UserCheckoutEventHandler(CheckoutService checkoutService) {
this.checkoutService = checkoutService;
}

@Incoming("checkout")
@Acknowledgment(Acknowledgment.Strategy.MANUAL)
@Retry(delay = 10, maxRetries = 5)
@Blocking
public CompletionStage<Void> Handle(Message<UserCheckoutEvent> message) {
try {
log.info("received user checkout event: " + message);
checkout.Checkout(message.getPayload());
return message.ack();
} catch (Exception e) {
log.error("Error processing user checkout event: " + message, e);
return message.nack(e);
}
@Outgoing("order-completed")
@Retry(maxRetries = 5)
@Transactional
public OrderCompleted Handle(UserCheckoutEvent message) {
log.info("received user checkout event: " + message);
var order = checkoutService.CreateOrderFromCheckout(message);

var orderCompleted = new OrderCompleted(order.getId(), order.getCartId(), order.getBuyerId(),
order.getTransactionId(),
order.getOrderedDate());

log.info("order completed: " + orderCompleted);
return orderCompleted;
}
}
Original file line number Diff line number Diff line change
@@ -43,6 +43,9 @@ public OrderItems mapDtoToOrderItems(CustomerBasketItem source) {
public OrderDto mapOrderToDto(Order order) {
var dto = new OrderDto();
dto.setId(order.getId());
dto.setCartId(order.getCartId());
dto.setTransactionId(order.getTransactionId());
dto.setCheckoutId(order.getCheckoutId());

var orderItems = order.getOrderItems()
.stream()
Original file line number Diff line number Diff line change
@@ -4,11 +4,8 @@
import java.util.UUID;

import jakarta.enterprise.context.ApplicationScoped;

import org.jurabek.restaurant.order.api.models.Order;

import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import io.quarkus.panache.common.Page;

/**
* OrdersRepository
@@ -23,7 +20,7 @@ public Order getByTransactionId(UUID transactionId) {
return find("transactionId", transactionId).firstResult();
}

public List<Order> fetchAll() {
return find("#Orders.fetchAll").page(Page.ofSize(30)).nextPage().list();
public List<Order> fetchAll(Integer start, Integer end) {
return this.findAll().range(start, end).list();
}
}
Original file line number Diff line number Diff line change
@@ -4,48 +4,34 @@

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.jurabek.restaurant.order.api.events.OrderCompleted;
import org.jurabek.restaurant.order.api.events.UserCheckoutEvent;
import org.jurabek.restaurant.order.api.mappers.OrdersMapper;
import org.jurabek.restaurant.order.api.models.Order;
import org.jurabek.restaurant.order.api.repositories.OrdersRepository;


@ApplicationScoped
public class CheckoutService {

private final OrdersRepository ordersRepository;
private final OrdersMapper mapper;

@Inject
@Channel("order-completed")
private Emitter<OrderCompleted> orderCompletedEventEmitter;

@Inject
public CheckoutService(OrdersRepository ordersRepository, OrdersMapper mapper) {
this.ordersRepository = ordersRepository;
this.mapper = mapper;
}

@Transactional
public void Checkout(UserCheckoutEvent checkoutInfo) {
public Order CreateOrderFromCheckout(UserCheckoutEvent checkoutInfo) {
var order = mapper.mapDtoToOrder(checkoutInfo.getCustomerBasket());
order.setTransactionId(checkoutInfo.getTransactionId());
order.setBuyerId(UUID.fromString(checkoutInfo.getCheckOutInfo().getUserId()));
order.setCheckoutId(checkoutInfo.getCheckoutId());

for (var orderItems : order.getOrderItems()) {
orderItems.setOrder(order);
}
ordersRepository.persist(order);

var event = new OrderCompleted(order.getId(), order.getCartId(), order.getBuyerId(), order.getTransactionId(),
order.getOrderedDate());

orderCompletedEventEmitter.send(event);
return order;
}

}
Original file line number Diff line number Diff line change
@@ -12,7 +12,9 @@ public interface OrdersService {

OrderDto getOrderByTransactionId(String transactionId);

List<OrderDto> getAll();
List<OrderDto> getAll(Integer offset, Integer limit);

Long getCount();

OrderDto getById(String orderId);
void Delete(String orderId);
Original file line number Diff line number Diff line change
@@ -27,8 +27,8 @@ public OrdersServicesIml(OrdersRepository ordersRepository, OrdersMapper mapper)
}

@Override
public List<OrderDto> getAll() {
return this.ordersRepository.fetchAll()
public List<OrderDto> getAll(Integer offset, Integer limit) {
return this.ordersRepository.fetchAll(offset, offset + limit)
.stream()
.map(o -> mapper.mapOrderToDto(o))
.collect(Collectors.toList());
@@ -61,4 +61,9 @@ public OrderDto getOrderByTransactionId(String transactionId) {
}
return mapper.mapOrderToDto(order);
}

@Override
public Long getCount() {
return ordersRepository.count();
}
}
Original file line number Diff line number Diff line change
@@ -75,10 +75,10 @@ public void getShouldReturnData(){
mockResults.add(new OrderDto());
mockResults.add(new OrderDto());

when(ordersService.getAll()).thenReturn(mockResults);
List<OrderDto> result = ordersController.getData();
Assertions.assertEquals(mockResults, result);
verify(ordersService, times(1)).getAll();
when(ordersService.getAll(0, 10)).thenReturn(mockResults);
var result = ordersController.getAll(0, 10);
Assertions.assertEquals(mockResults, result.getOrders());
verify(ordersService, times(1)).getAll(0, 10);
}

@Test
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@
import org.jurabek.restaurant.order.api.models.OrderItems;
import org.jurabek.restaurant.order.api.repositories.OrdersRepository;


import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;
import jakarta.inject.Inject;
@@ -42,17 +41,17 @@ public void setup() {

// @Test
// public void CreateShouldCreateWhenCustomerBasketDto() {
// var customerBasketDto = new CustomerBasket();
// var order = new Order();
// var orderItems = new ArrayList<OrderItems>();
// orderItems.add(new OrderItems());
// order.setOrderItems(orderItems);
// var customerBasketDto = new CustomerBasket();
// var order = new Order();
// var orderItems = new ArrayList<OrderItems>();
// orderItems.add(new OrderItems());
// order.setOrderItems(orderItems);

// when(mapper.mapDtoToOrder(customerBasketDto)).thenReturn(order);
// when(mapper.mapDtoToOrder(customerBasketDto)).thenReturn(order);

// ordersService.Create(customerBasketDto);
// ordersService.Create(customerBasketDto);

// verify(ordersRepository, times(1)).persist(order);
// verify(ordersRepository, times(1)).persist(order);
// }

@Test
@@ -62,10 +61,10 @@ public void GetAllShouldReturnAllOrderDtos() {

var dto = new OrderDto();

when(ordersRepository.fetchAll()).thenReturn(orders);
when(ordersRepository.fetchAll(0, 10)).thenReturn(orders);
when(mapper.mapOrderToDto(order)).thenReturn(dto);

List<OrderDto> result = ordersService.getAll();
List<OrderDto> result = ordersService.getAll(0, 10);

Assertions.assertEquals(1, result.size());
Assertions.assertEquals(result.get(0), dto);
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ function DesktopNav() {
<Home className="h-5 w-5" />
</NavItem>

<NavItem href="#" label="Orders">
<NavItem href="/orders" label="Orders">
<ShoppingCart className="h-5 w-5" />
</NavItem>

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Button } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { TableCell, TableRow } from "@/components/ui/table";
import { calculateTotalPrice, Order } from "@/lib/types/order";
import { MoreHorizontal } from "lucide-react";

export function OrderRow({ order }: { order: Order }) {
return (
<TableRow>
<TableCell className="font-medium">{order.orderedDate.toString()}</TableCell>
<TableCell className="font-medium">{calculateTotalPrice(order)}</TableCell>
<TableCell className="font-medium">{order.cartId}</TableCell>
<TableCell className="font-medium">{order.checkoutId}</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup="true" size="icon" variant="ghost">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>
<form action={() => {}}>
<button type="submit">Delete</button>
</form>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";

import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Order } from "@/lib/types/order";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useRouter } from "next/navigation";
import { OrderRow } from "./order-table-row";

export function OrdersTable({
orders,
offset,
totalProducts,
productsPerPage,
}: {
orders: Order[];
offset: number;
totalProducts: number;
productsPerPage: number;
}) {
let router = useRouter();
function prevPage() {
router.back();
}

function nextPage() {
router.push(`/orders?offset=${offset}`, { scroll: false });
}

return (
<Card>
<CardHeader>
<CardTitle>Orders</CardTitle>
<CardDescription>View all orders.</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Order Date</TableHead>
<TableHead className="hidden md:table-cell">
Total Price
</TableHead>
<TableHead className="hidden md:table-cell">Cart ID</TableHead>
<TableHead className="hidden md:table-cell">
Checkout ID
</TableHead>
<TableHead>
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{orders.map((order) => (
<OrderRow key={order.id} order={order} />
))}
</TableBody>
</Table>
</CardContent>
<CardFooter>
<form className="flex items-center w-full justify-between">
<div className="text-xs text-muted-foreground">
Showing{" "}
<strong>
{Math.min(offset - productsPerPage, totalProducts) + 1}-{offset}
</strong>{" "}
of <strong>{totalProducts}</strong> products
</div>
<div className="flex">
<Button
formAction={prevPage}
variant="ghost"
size="sm"
type="submit"
disabled={offset === productsPerPage}
>
<ChevronLeft className="mr-2 h-4 w-4" />
Prev
</Button>
<Button
formAction={nextPage}
variant="ghost"
size="sm"
type="submit"
disabled={offset + productsPerPage > totalProducts}
>
Next
<ChevronRight className="ml-2 h-4 w-4" />
</Button>
</div>
</form>
</CardFooter>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { fetchOrders } from "@/lib/fetch";
import { OrdersTable } from "./orders-table";

export default async function OrdersPage({
searchParams,
}: {
searchParams: { q: string; offset: string };
}) {
const search = searchParams.q ?? "";
const offset = searchParams.offset ?? 0;
const productsPerPage = 10;

const {
orders,
total,
offset: currOffset,
} = await fetchOrders(Number(offset), productsPerPage);

const newOffset = currOffset + productsPerPage;

return (
<div>
<OrdersTable
orders={orders}
offset={newOffset}
totalProducts={total}
productsPerPage={productsPerPage}
/>
</div>
);
}
12 changes: 12 additions & 0 deletions src/backend/services/web.admin/dashboard-app/src/lib/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CatalogItems, CatalogItemsScheme, Categories, CategoriesScheme } from "./types/catalog";
import { Orders } from "./types/order";

export type SelectProduct = {
status: "active" | "inactive" | "archived";
@@ -71,3 +72,14 @@ export async function fetchCategories(): Promise<Categories> {
const categories: Categories = CategoriesScheme.parse(await res.json());
return categories;
}

export async function fetchOrders(offset: number, limit: number): Promise<Orders> {
const apiUrl = `${process.env.INTERNAL_API_BASE_URL}/order/api/v1/orders?offset=${offset}&limit=${limit}`;
const res = await fetch(apiUrl);
if (!res.ok) {
throw new Error('Failed to fetch orders data');
}

const orders = await res.json();
return orders;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as z from "zod";

export const OrderItemSchema = z.object({
"id": z.string(),
"unitPrice": z.number(),
"units": z.number(),
"productId": z.number(),
"productName": z.null(),
});
export type OrderItem = z.infer<typeof OrderItemSchema>;

export const OrderSchema = z.object({
"id": z.string(),
"orderedDate": z.coerce.date(),
"cartId": z.string(),
"transactionId": z.string(),
"checkoutId": z.string(),
"orderItems": z.array(OrderItemSchema),
});
export type Order = z.infer<typeof OrderSchema>;

export const OrdersSchema = z.object({
"orders": z.array(OrderSchema),
"total": z.number(),
"offset": z.number(),
"limit": z.number(),
});
export type Orders = z.infer<typeof OrdersSchema>;

export function calculateTotalPrice(order: Order): number {
return order.orderItems.reduce((total, item) => total + item.unitPrice, 0);
}
Original file line number Diff line number Diff line change
@@ -3,4 +3,4 @@ import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
}