import { DEFAULT_STANDARDS_DATA, LevelCalculator, Standards, } from "@blade-and-brawn/calculator"; import { ActivityPerformanceSchema, PlayerSchema, type ActivityPerformance, type Player, } from "@blade-and-brawn/domain" import { cors } from "@elysiajs/cors"; import { Elysia, NotFoundError } from "elysia"; import { PrintfulService, SyncService, WebflowService, Printful, Webflow, FetchError, } from "@blade-and-brawn/commerce"; import zipcodesUs from "zipcodes-us"; import z from "zod"; const levelCalculator = new LevelCalculator( new Standards(DEFAULT_STANDARDS_DATA), ); export const app = new Elysia() .use( cors({ origin: [ // WEBSITE // ----------- // production /^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.com$/i, // testing /^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.webflow\.io$/i, // PORTAL // ----------- // production /^https?:\/\/blade-and-brawn\.fly\.dev$/i, // development "http://localhost:5173", ], }), ) .error({ FetchError, }) .onError(async ({ code, error }) => { switch (code) { case "FetchError": console.error(error.message, await error.parse()); return error; } }) .onAfterHandle(({ request, set }) => { console.log( JSON.stringify({ lvl: "info", msg: "req", method: request.method, path: new URL(request.url).pathname, status: set.status ?? 200, }), ); }) .get("/", () => "Hello world") // CALCULATOR .post("/calculate", async ({ body }) => { const output = { levels: levelCalculator.calculate(body.player, body.activityPerformances), }; // console.log({ log: query?.log }); // if (query?.log === "true") { // void fetch("https://kv-logger.xominus.workers.dev", { // method: "POST", // headers: { "content-type": "application/json" }, // body: JSON.stringify({ output, player, activityPerformances }), // }).catch(() => {}); // } return output; }, { body: z.object({ player: PlayerSchema, activityPerformances: z.array(ActivityPerformanceSchema) }) }) // COMMERCE // TODO: protect with api key/token .group("/products", (app) => app .group("/sync", (app) => app // Sync status .get("/", async ({ }) => SyncService.state) // Full sync .post("/", async ({ set }) => { if (SyncService.state.isSyncing) { set.status = 409; return { error: "Sync already in progress" }; } await SyncService.sync(); return { ok: true }; }) // Per-product sync .post("/:printfulProductId", async ({ params, set }) => { if (SyncService.state.isSyncing) { set.status = 409; return { error: "Sync already in progress" }; } await SyncService.sync(params.printfulProductId); return { ok: true }; }, { params: z.object({ printfulProductId: z.number() }) }), ) .get("/:printfulProductId", async ({ params }) => { const printfulProductId = +params.printfulProductId; const printfulProduct = await PrintfulService.Products.get(printfulProductId); if (!printfulProduct) { return new NotFoundError(); } const webflowProductId = printfulProduct.sync_product.external_id.split("-")[0]; if (!webflowProductId) { return new NotFoundError(); } const webflowProduct = await WebflowService.Products.get(webflowProductId); return { printfulProduct, webflowProduct }; }), ) // WEBHOOKS .post("/webhook/printful", async ({ body }) => { const payload = body as Printful.Webhook.EventPayload; switch (payload.type) { case Printful.Webhook.Event.ProductUpdated: { const printfulProduct = payload.data.sync_product; await SyncService.sync(printfulProduct.id); break; } case Printful.Webhook.Event.ProductDeleted: { await WebflowService.Products.remove( payload.data.sync_product.external_id, ); break; } case Printful.Webhook.Event.PackageShipped: { const webflowOrderId = payload.data.order.external_id; const shipInfo = payload.data.shipment; await WebflowService.Orders.update(webflowOrderId, { shippingTrackingURL: shipInfo.tracking_url, shippingTracking: shipInfo.tracking_number, shippingProvider: shipInfo.carrier, }); await WebflowService.Orders.fulfill(webflowOrderId, { sendOrderFulfilledEmail: true, }); break; } } return { ok: true }; }) .post("/webhook/webflow", async ({ request, body, set }) => { if (!WebflowService.Util.verifyWebflowSignature(request, body)) { set.status = 400; return "Invalid signature"; } const payload = body as Webflow.Webhook.EventPayload; switch (payload.triggerType) { case Webflow.Webhook.Event.OrderCreated: { const webflowOrder = payload.payload; await PrintfulService.Orders.create({ external_id: webflowOrder.orderId, // TODO: derive from webflow shipping: "STANDARD", recipient: { name: webflowOrder.shippingAddress.addressee, address1: webflowOrder.shippingAddress.line1, address2: webflowOrder.shippingAddress.line2, city: webflowOrder.shippingAddress.city, state_code: zipcodesUs.find( webflowOrder.shippingAddress.postalCode.split( "-", )[0] ?? "", ).stateCode, country_code: webflowOrder.shippingAddress.country, zip: webflowOrder.shippingAddress.postalCode, }, items: webflowOrder.purchasedItems.map( (webflowOrderSku) => ({ external_variant_id: webflowOrderSku.variantId, quantity: webflowOrderSku.count, }), ), }); break; } } return { ok: true }; }); app.listen(3000);