From 1a6f6b3bd191cf9d521cf115cf72a6165d1dd726 Mon Sep 17 00:00:00 2001 From: Harvey Zuccon Date: Sat, 23 May 2026 18:06:41 +0200 Subject: [PATCH] Review AI Code. Documented parts that may want to be changed in the future --- src/index.ts | 25 ++++++++---- src/routes/items.ts | 4 ++ src/routes/orders.ts | 38 +++++++++++++++++++ src/services/config.ts | 1 + .../002-order-invitation-and-seed.ts | 1 + src/services/http-router.ts | 3 ++ src/services/invitation-sync-client.ts | 1 + src/services/order-invitation-tracker.ts | 4 +- src/services/order-payment-service.ts | 1 + src/services/sse-broadcaster.ts | 6 +++ src/utils/logger.ts | 16 ++++++++ 11 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7921f20..72fb54b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,9 +21,15 @@ type VendingMachineDeps = { }; export class VendingMachine { + /** + * Create a new VendingMachine instance + * @param config - The configuration for the vending machine + * @returns The VendingMachine instance + */ static async from(config: Config) { const debug = new Logger("vending-machine"); - + + // Debug the config. debug("Config: %O", config); debug("Creating engine"); @@ -34,13 +40,16 @@ export class VendingMachine { debug("Creating database"); const database = new Database({ config: config.database, debug }); + debug("Creating migration service"); const migrationService = new MigrationService(database); debug("Migrating database to latest"); await migrationService.migrateToLatest(); + // TODO: Do we need to define the trackers outside of the order payment service? debug("Creating trackers"); const trackers = new Map(); + debug("Creating order payment service"); const orderPaymentService = await OrderPaymentService.create({ engine, @@ -81,18 +90,18 @@ export class VendingMachine { private constructor(private readonly deps: VendingMachineDeps) {} + /** + * Start the vending machine + * @returns A promise that resolves when the vending machine has started + */ public async start() { await this.deps.httpService.start(); } - - public async stop() { - for (const tracker of this.deps.trackers.values()) { - tracker.stop(); - } - await this.deps.engine.stop(); - } } +/** + * Create an instance of the vending machine and start it. + */ VendingMachine.from(Config.fromEnv()) .then((vendingMachine) => vendingMachine.start()) .catch((error) => { diff --git a/src/routes/items.ts b/src/routes/items.ts index 7e28eae..1bba34d 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -14,6 +14,10 @@ export type ItemsRouteDeps = { export class ItemsRoute { public constructor(private readonly deps: ItemsRouteDeps) {} + /** + * Get the routes for the items route + * @returns The routes for the items route + */ public async getRoutes(): Promise> { return [ { diff --git a/src/routes/orders.ts b/src/routes/orders.ts index 05febf8..ccedc1f 100644 --- a/src/routes/orders.ts +++ b/src/routes/orders.ts @@ -19,6 +19,10 @@ export type OrdersRouteDeps = { export class OrdersRoute { public constructor(private readonly deps: OrdersRouteDeps) {} + /** + * Get the routes for the orders route + * @returns The routes for the orders route + */ public async getRoutes(): Promise> { return [ { @@ -39,6 +43,14 @@ export class OrdersRoute { ]; } + /** + * Get all orders from the database + * + * TODO: Do we need this? It should exist on the wallet/state/engine + * @param request + * @param reply + * @returns + */ private async getOrders(_request: FastifyRequest, reply: FastifyReply) { const orders = await this.deps.database.db .selectFrom("orders") @@ -53,19 +65,28 @@ export class OrdersRoute { ); } + /** + * Get an order from the database by id + * @param request + * @param reply + * @returns + */ private async getOrder(request: FastifyRequest, reply: FastifyReply) { const { id } = OrdersRoute.getOrderSchema.parse(request.params); + // Get the order from the database. const order = await this.deps.database.db .selectFrom("orders") .selectAll() .where("id", "=", id) .executeTakeFirst(); + // If the order is not found, return a 404 error. if (!order) { return reply.status(404).send({ error: "Order not found" }); } + // Return the order. return reply.send({ ...order, items: JSON.parse(order.items), @@ -73,29 +94,46 @@ export class OrdersRoute { }); } + /** + * Create an order and create a payment "channel" for it by creating an invitation and sending it to the sync server. + * @param request + * @param reply + * @returns + */ private async createOrder(request: FastifyRequest, reply: FastifyReply) { const { items: itemsInput } = OrdersRoute.createOrderSchema.parse( request.body, ); try { + // Create the order and payment "channel". const result = await this.deps.orderPaymentService.createOrder(itemsInput); return reply.status(201).send(result); } catch (error) { + // If the error is an order payment error, return a 400 error. if (error instanceof OrderPaymentError) { return reply.status(error.statusCode).send({ error: error.message }); } + // If the error is not an order payment error, return a 500 error. this.deps.debug("Failed to create order: %o", error); return reply.status(500).send({ error: "Failed to create order" }); } } + /** + * Schema for getting an order by id + * @returns The schema for getting an order by id + */ static getOrderSchema = z.object({ id: z.string(), }); + /** + * Schema for creating an order + * @returns The schema for creating an order + */ static createOrderSchema = z.object({ items: z.array( z.object({ diff --git a/src/services/config.ts b/src/services/config.ts index 2adf704..747791e 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -1,6 +1,7 @@ import "dotenv/config"; import { z } from "zod"; +// TODO: Decide if zod schema is best. So far, im liking it in the config file. const configSchema = z.object({ engine: z.object({ mnemonic: z.string().min(1, "ENGINE_MNEMONIC is required"), diff --git a/src/services/database/migrations/002-order-invitation-and-seed.ts b/src/services/database/migrations/002-order-invitation-and-seed.ts index cfb7465..4446e3b 100644 --- a/src/services/database/migrations/002-order-invitation-and-seed.ts +++ b/src/services/database/migrations/002-order-invitation-and-seed.ts @@ -17,6 +17,7 @@ export async function up(db: Kysely): Promise { .column("invitation_identifier") .execute(); + // Hard-Insert items for demo purposes. const seedItems = [ { name: "Cola", diff --git a/src/services/http-router.ts b/src/services/http-router.ts index 25c78f5..f1dc04c 100644 --- a/src/services/http-router.ts +++ b/src/services/http-router.ts @@ -11,6 +11,9 @@ export interface APIRoutes { getRoutes(): Promise>; } +// TODO: Decide if this complexity in the zod schemas is worth it. +// They are pretty big, and not pretty to look at. +// The value they add is that they allow us to define the defaults in a single place and dont bloat the constructor. // Zod schema for the API routes. export const apiRoutesSchema = z.array(z.custom()); diff --git a/src/services/invitation-sync-client.ts b/src/services/invitation-sync-client.ts index d2f20ae..594ac6b 100644 --- a/src/services/invitation-sync-client.ts +++ b/src/services/invitation-sync-client.ts @@ -66,6 +66,7 @@ export class InvitationSyncClient { /** * Stateless POST — publish invitation to sync server. + * TODO: Think about race-conditions here? I think the current sync-server "diffs" them though */ async publish(invitation: XOInvitation): Promise { const response = await fetch(`${this.normalizedBaseUrl()}/invitations`, { diff --git a/src/services/order-invitation-tracker.ts b/src/services/order-invitation-tracker.ts index abf3d88..01481dc 100644 --- a/src/services/order-invitation-tracker.ts +++ b/src/services/order-invitation-tracker.ts @@ -104,6 +104,8 @@ export class OrderInvitationTracker { return; } + // TODO: This is the incorrect logic. We probably want to try and either wait for the blockchain or wait for the user to "sign" the invitation + // Basing it off of the commit count is flawed and useless. const hasCustomerActivity = mergedCommits.length > this.initialCommitCount; if (order.status === "pending" && hasCustomerActivity) { @@ -116,7 +118,7 @@ export class OrderInvitationTracker { this.deps.debug("Order %s marked paid", this.deps.orderId); this.dispenseTimer = setTimeout(() => { - void this.completeOrder(); + this.completeOrder(); }, 1500); } } diff --git a/src/services/order-payment-service.ts b/src/services/order-payment-service.ts index 1727687..23f74bf 100644 --- a/src/services/order-payment-service.ts +++ b/src/services/order-payment-service.ts @@ -172,6 +172,7 @@ export class OrderPaymentService { roleIdentifier: "merchant", value: receiptSummary, }, + // TODO: Figure out a better/user-friendly way to display the line items. { variableIdentifier: "lineItemsJson", roleIdentifier: "merchant", diff --git a/src/services/sse-broadcaster.ts b/src/services/sse-broadcaster.ts index 22fec9f..7428918 100644 --- a/src/services/sse-broadcaster.ts +++ b/src/services/sse-broadcaster.ts @@ -2,6 +2,12 @@ import type { FastifyReply, FastifyRequest } from "fastify"; import debug, { type Debugger } from "debug"; +/** + * TODO: This file was pulled directly from a more advanced project which has multi-user support and replay functionality. + * Its pretty unlikely that you would need replay support for this kind of application because the frontend would likely just grab the data + * and this isn't super real-time. But, I've left the functionality in for now because its better to have the functionality and not need it during development. + */ + /** * Represents an event stored in the history buffer. * Used for replaying missed events to reconnecting clients. diff --git a/src/utils/logger.ts b/src/utils/logger.ts index b5bdb4c..ef288dd 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -12,6 +12,10 @@ export interface Logger { (...args: unknown[]): void; } +/** + * Logger class, similar to 'debug' library but you can call `instanceof Logger` to check if a value is a Logger instance. + * Also, it wont throw if you dont enable debugging but one of the modules want a debug logger. - This feels like a bug in debug though + */ export class Logger { public readonly namespace!: string; @@ -21,6 +25,7 @@ export class Logger { namespace: string, handler: LogHandler = Debug(namespace), ) { + // Create a logger function that calls the handler. const logger = ((...args: Parameters): void => { handler(...args); }) as Logger; @@ -31,6 +36,7 @@ export class Logger { */ Object.setPrototypeOf(logger, new.target.prototype); + // Define the properties for the logger. Object.defineProperties(logger, { namespace: { value: namespace, @@ -44,6 +50,11 @@ export class Logger { return logger; } + /** + * Extend the logger with a child namespace + * @param childNamespace - The child namespace to extend the logger with + * @returns The extended logger + */ public extend(childNamespace: string): Logger { return new Logger( childNamespace, @@ -51,6 +62,11 @@ export class Logger { ); } + /** + * Check if a value is a Logger instance + * @param value - The value to check + * @returns True if the value is a Logger instance, false otherwise + */ static isLogger(value: unknown): value is Logger { return value instanceof Logger; }