This commit is contained in:
Dominic Ferrando
2025-10-18 08:27:51 -04:00
parent 33eb924173
commit 15aa6e44f0
6 changed files with 93 additions and 15 deletions
+6 -1
View File
@@ -1,4 +1,9 @@
import { treaty } from "@elysiajs/eden"; import { treaty } from "@elysiajs/eden";
import type { App } from "./server/types"; import type { App } from "./server/types";
export const api = treaty<App>('http://localhost:5173').api; const baseURL =
import.meta.env.PROD
? "https://blade-and-brawn.fly.dev"
: "http://localhost:5173";
export const api = treaty<App>(baseURL).api;
+52 -9
View File
@@ -1,6 +1,6 @@
import { Elysia, NotFoundError } from "elysia"; import { Elysia, NotFoundError } from "elysia";
import { cors } from "@elysiajs/cors"; import { cors } from "@elysiajs/cors";
import { LevelCalculator, Standards, type ActivityStandards } from "$lib/services/calculator/main"; import { LevelCalculator, Standards, type ActivityStandards, type LevelCalculatorOutput } from "$lib/services/calculator/main";
import type { ActivityPerformance, Player } from "$lib/services/calculator/util"; import type { ActivityPerformance, Player } from "$lib/services/calculator/util";
import rawStandards from "$lib/data/standards.json" assert { type: "json" } import rawStandards from "$lib/data/standards.json" assert { type: "json" }
import { Printful, Webflow } from "../services/commerce/util/types"; import { Printful, Webflow } from "../services/commerce/util/types";
@@ -9,8 +9,24 @@ import SyncService from "../services/commerce/sync";
import PrintfulService from "../services/commerce/printful"; import PrintfulService from "../services/commerce/printful";
import { FetchError, getStateFromZip } from "../services/commerce/util/misc"; import { FetchError, getStateFromZip } from "../services/commerce/util/misc";
const MAIN_STANDARDS = new Standards(rawStandards as ActivityStandards);
const levelCalculator = new LevelCalculator(MAIN_STANDARDS);
export const app = new Elysia({ prefix: "/api" }) export const app = new Elysia({ prefix: "/api" })
.use(cors({ origin: '*' })) .use(
cors({
origin: [
// bladeandbrawn.com (apex + any subdomain)
/^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.com$/i,
// Webflow preview domains
/^https?:\/\/([a-z0-9-]+\.)?bladeandbrawn\.webflow\.io$/i,
// Fly app host (this one already works)
/^https?:\/\/blade-and-brawn\.fly\.dev$/i,
// Local dev
"http://localhost:5173",
]
})
)
.error({ .error({
FetchError FetchError
@@ -24,30 +40,55 @@ export const app = new Elysia({ prefix: "/api" })
} }
}) })
.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") .get("/", () => "Hello world")
// CALCULATOR // CALCULATOR
.post("/calculate", async ({ body }) => { .post("/calculate", async ({ body, query }) => {
const MAIN_STANDARDS = new Standards(rawStandards as ActivityStandards);
interface CalcRequest { interface CalcRequest {
player: Player; player: Player;
activityPerformances: ActivityPerformance[]; activityPerformances: ActivityPerformance[];
} }
const { player, activityPerformances } = body as CalcRequest; const { player, activityPerformances } = body as CalcRequest;
const levelCalculator = new LevelCalculator(MAIN_STANDARDS); const output = { levels: levelCalculator.calculate(player, activityPerformances) };
return { levels: levelCalculator.calculate(player, 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(e => { });
}
return output;
}) })
// COMMERCE // COMMERCE
// TODO: protect with api key/token
.group("/products", app => app .group("/products", app => app
.group("/sync", app => app .group("/sync", app => app
.get("/", async ({ }) => SyncService.state) .get("/", async ({ }) => SyncService.state)
.post("/:printfulProductId?", async ({ params }) => { .post("/:printfulProductId?", async ({ params, set }) => {
if (SyncService.state.isSyncing) {
set.status = 409;
return { error: "Sync already in progress" };
}
const printfulProductId = params.printfulProductId ? const printfulProductId = params.printfulProductId ?
+params.printfulProductId : +params.printfulProductId :
undefined; undefined;
await SyncService.sync(printfulProductId); await SyncService.sync(printfulProductId);
return { ok: true };
}) })
) )
.get("/:printfulProductId", async ({ params }) => { .get("/:printfulProductId", async ({ params }) => {
@@ -68,8 +109,6 @@ export const app = new Elysia({ prefix: "/api" })
.post("/webhook/printful", async ({ body }) => { .post("/webhook/printful", async ({ body }) => {
const payload = body as Printful.Webhook.EventPayload; const payload = body as Printful.Webhook.EventPayload;
console.log("Hit webhook", payload.type);
switch (payload.type) { switch (payload.type) {
case Printful.Webhook.Event.ProductUpdated: { case Printful.Webhook.Event.ProductUpdated: {
const printfulProduct = payload.data.sync_product; const printfulProduct = payload.data.sync_product;
@@ -93,6 +132,8 @@ export const app = new Elysia({ prefix: "/api" })
break; break;
} }
} }
return { ok: true };
}) })
.post("/webhook/webflow", async ({ request, body, set }) => { .post("/webhook/webflow", async ({ request, body, set }) => {
if (!WebflowService.Util.verifyWebflowSignature(request, body)) { if (!WebflowService.Util.verifyWebflowSignature(request, body)) {
@@ -130,4 +171,6 @@ export const app = new Elysia({ prefix: "/api" })
break; break;
} }
} }
return { ok: true };
}); });
+3 -1
View File
@@ -198,7 +198,9 @@ export class Standards {
0.1, 0.1,
weightSkew: 0, weightSkew: 0,
ageModifier: 0.5, ageModifier: 0.5,
difficultyModifier: 0 difficultyModifier: activity === Activity.BroadJump ?
0.05 :
0
}, },
]) ])
) as Record<Activity, StandardsConfig["activity"][Activity]>; ) as Record<Activity, StandardsConfig["activity"][Activity]>;
+29
View File
@@ -101,6 +101,35 @@ export default class PrintfulService {
throw new FetchError("Failed to create printful order", res); throw new FetchError("Failed to create printful order", res);
} }
} }
static async getAll(offset: number = 0): Promise<Printful.Orders.Order[]> {
const allPrintfulOrders: Printful.Orders.Order[] = [];
while (true) {
try {
const res = await fetch(`${env().API_URL}/orders?offset=${offset}`, {
method: "GET",
headers: { ...env().AUTH_HEADERS }
})
if (!res.ok) {
throw new FetchError("Failed to get all Printful orders", res);
}
const payload: Printful.Products.MetaDataMulti<Printful.Orders.Order> = await res.json();
allPrintfulOrders.push(...payload.result);
offset = allPrintfulOrders.length;
if (allPrintfulOrders.length >= payload.paging.total)
break;
} catch (error) {
throw error;
}
}
return allPrintfulOrders;
}
} }
static Util = class { static Util = class {
+2 -3
View File
@@ -46,9 +46,8 @@ const computedPerformances: ActivityPerformance[] = [
}, },
]; ];
const wOrders = await WebflowService.Orders.getAll(); const pOrders = await PrintfulService.Orders.getAll();
console.log(pOrders);
console.log(wOrders);
// wProduct.product.fieldData.name = "Rogue Title Hoodie [White] / XS"; // wProduct.product.fieldData.name = "Rogue Title Hoodie [White] / XS";
// //
+1 -1
View File
@@ -3,5 +3,5 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [tailwindcss(), sveltekit()], plugins: [tailwindcss(), sveltekit()]
}); });