Review AI Code. Documented parts that may want to be changed in the future
This commit is contained in:
25
src/index.ts
25
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<string, OrderInvitationTracker>();
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -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<Array<RouteOptions>> {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -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<Array<RouteOptions>> {
|
||||
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({
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -17,6 +17,7 @@ export async function up(db: Kysely<Database>): Promise<void> {
|
||||
.column("invitation_identifier")
|
||||
.execute();
|
||||
|
||||
// Hard-Insert items for demo purposes.
|
||||
const seedItems = [
|
||||
{
|
||||
name: "Cola",
|
||||
|
||||
@@ -11,6 +11,9 @@ export interface APIRoutes {
|
||||
getRoutes(): Promise<Array<RouteOptions>>;
|
||||
}
|
||||
|
||||
// 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<APIRoutes>());
|
||||
|
||||
|
||||
@@ -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<XOInvitation> {
|
||||
const response = await fetch(`${this.normalizedBaseUrl()}/invitations`, {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Debugger>): 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user