import type { Address, AuthUser } from "@orderops/shared"; import type { FastifyPluginAsync, FastifyRequest } from "fastify"; import { requireEmployeeUser } from "../auth/current-user"; import type { DatabaseContext } from "../db/database"; import { addOrderNote } from "./add-order-note"; import { cancelOrder } from "./cancel-order"; import { getOrder } from "./get-order"; import { listAuditEvents } from "./list-audit-events"; import { listOrders } from "./list-orders"; import { changeOrderItemQuantity } from "./update-order-item"; import { changeShippingAddress } from "./update-shipping-address"; interface OrderRouteOptions { db: DatabaseContext; } interface SearchQuery { query?: string; } interface IdParams { orderId: string; } interface OrderItemParams extends IdParams { itemId: string; } interface QuantityBody { quantity: number; } interface CancelBody { reason?: string; } interface NoteBody { body: string; } type AuthenticatedRequest = FastifyRequest & { currentUser: AuthUser }; export const registerOrderRoutes: FastifyPluginAsync = async (app, options) => { app.addHook("preHandler", async (request, reply) => { const user = await requireEmployeeUser(options.db, request, reply); if (!user) { return reply; } (request as AuthenticatedRequest).currentUser = user; }); app.get<{ Querystring: SearchQuery }>("/orders", { schema: { querystring: { type: "object", properties: { query: { type: "string" } } } } }, async (request) => listOrders(options.db, request.query.query)); app.get<{ Params: IdParams }>("/orders/:orderId", async (request, reply) => { const order = await getOrder(options.db, request.params.orderId); if (!order) { return reply.code(404).send({ message: "Order not found." }); } return order; }); app.get<{ Params: IdParams }>("/orders/:orderId/audit-events", async (request) => listAuditEvents(options.db, request.params.orderId)); app.post<{ Params: IdParams; Body: NoteBody }>("/orders/:orderId/notes", { schema: { body: { type: "object", required: ["body"], properties: { body: { type: "string", minLength: 1, maxLength: 1999 } } } } }, async (request, reply) => { const currentUser = (request as AuthenticatedRequest).currentUser; const result = await addOrderNote(options.db, { orderId: request.params.orderId, authorUserId: currentUser.id, body: request.body.body }); if (result.statusCode !== 200) { return reply.code(result.statusCode).send({ message: result.message }); } return result.note; }); app.post<{ Params: OrderItemParams; Body: QuantityBody }>("/orders/:orderId/items/:itemId/quantity", { schema: { body: { type: "object", required: ["quantity"], properties: { quantity: { type: "number" } } } } }, async (request, reply) => { const currentUser = (request as AuthenticatedRequest).currentUser; const result = await changeOrderItemQuantity(options.db, { orderId: request.params.orderId, itemId: request.params.itemId, quantity: request.body.quantity, actor: currentUser.username }); if (result.statusCode !== 200) { return reply.code(result.statusCode).send({ message: result.message }); } return result.order; }); app.post<{ Params: IdParams; Body: Address }>("/orders/:orderId/shipping-address", { schema: { body: { type: "object", required: ["fullName", "line1", "city", "state", "postalCode", "country"], properties: { fullName: { type: "string", minLength: 1 }, line1: { type: "string", minLength: 1 }, line2: { type: ["string", "null"] }, city: { type: "string", minLength: 1 }, state: { type: "string", minLength: 1 }, postalCode: { type: "string", minLength: 1 }, country: { type: "string", minLength: 1 } } } } }, async (request, reply) => { const currentUser = (request as AuthenticatedRequest).currentUser; const result = await changeShippingAddress(options.db, { orderId: request.params.orderId, address: { fullName: request.body.fullName, line1: request.body.line1, line2: request.body.line2 ?? null, city: request.body.city, state: request.body.state, postalCode: request.body.postalCode, country: request.body.country }, actor: currentUser.username }); if (result.statusCode !== 200) { return reply.code(result.statusCode).send({ message: result.message }); } return result.order; }); app.post<{ Params: IdParams; Body: CancelBody }>("/orders/:orderId/cancel", { schema: { body: { type: "object", properties: { reason: { type: "string" } } } } }, async (request, reply) => { const currentUser = (request as AuthenticatedRequest).currentUser; const result = await cancelOrder(options.db, { orderId: request.params.orderId, reason: request.body.reason, actor: currentUser.username }); if (result.statusCode !== 200) { return reply.code(result.statusCode).send({ message: result.message }); } return result.order; }); };