Fix tabbing

This commit is contained in:
Dominic Ferrando
2026-06-14 13:14:01 -04:00
parent 42f47c8601
commit 05dc26adb5
31 changed files with 9137 additions and 8508 deletions
+12 -12
View File
@@ -1,14 +1,14 @@
{
"name": "@blade-and-brawn/api",
"module": "index.ts",
"type": "module",
"private": true,
"dependencies": {
"@elysiajs/cors": "^1.4.1",
"elysia": "^1.4.27",
"zipcodes-us": "^1.1.2",
"@blade-and-brawn/calculator": "workspace:*",
"@blade-and-brawn/commerce": "workspace:*",
"ml-levenberg-marquardt": "^5.0.0"
}
"name": "@blade-and-brawn/api",
"module": "index.ts",
"type": "module",
"private": true,
"dependencies": {
"@elysiajs/cors": "^1.4.2",
"elysia": "^1.4.28",
"zipcodes-us": "^1.1.3",
"@blade-and-brawn/calculator": "workspace:*",
"@blade-and-brawn/commerce": "workspace:*",
"ml-levenberg-marquardt": "^5.0.1"
}
}
+186 -173
View File
@@ -1,195 +1,208 @@
import {
DEFAULT_STANDARDS_DATA,
LevelCalculator,
Standards,
type ActivityPerformance,
type Player,
DEFAULT_STANDARDS_DATA,
LevelCalculator,
Standards,
type ActivityPerformance,
type Player,
} from "@blade-and-brawn/calculator";
import { cors } from "@elysiajs/cors";
import { Elysia, NotFoundError } from "elysia";
import { PrintfulService, SyncService, WebflowService, Printful, Webflow, FetchError } from "@blade-and-brawn/commerce";
import {
PrintfulService,
SyncService,
WebflowService,
Printful,
Webflow,
FetchError,
} from "@blade-and-brawn/commerce";
import zipcodesUs from "zipcodes-us";
const levelCalculator = new LevelCalculator(new Standards(DEFAULT_STANDARDS_DATA));
const levelCalculator = new LevelCalculator(
new Standards(DEFAULT_STANDARDS_DATA),
);
export const app = new Elysia()
.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 portal
"http://localhost:5173",
],
}),
)
.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 portal
"http://localhost:5173",
],
}),
)
.error({
FetchError,
})
.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, query }) => {
interface CalcRequest {
player: Player;
activityPerformances: ActivityPerformance[];
}
const { player, activityPerformances } = body as CalcRequest;
const output = {
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(() => {});
// }
return output;
})
// COMMERCE
// TODO: protect with api key/token
.group("/products", (app) =>
app
.group("/sync", (app) =>
app
.get("/", async ({ }) => SyncService.state)
.post("/:printfulProductId?", async ({ params, set }) => {
if (SyncService.state.isSyncing) {
set.status = 409;
return { error: "Sync already in progress" };
}
const printfulProductId = params.printfulProductId
? +params.printfulProductId
: undefined;
await SyncService.sync(printfulProductId);
return { ok: true };
}),
)
.get("/:printfulProductId", async ({ params }) => {
const printfulProductId = +params.printfulProductId;
const printfulProduct =
await PrintfulService.Products.get(printfulProductId);
if (!printfulProduct) {
return new NotFoundError();
.onError(async ({ code, error }) => {
switch (code) {
case "FetchError":
console.error(error.message, await error.parse());
return error;
}
})
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,
.onAfterHandle(({ request, set }) => {
console.log(
JSON.stringify({
lvl: "info",
msg: "req",
method: request.method,
path: new URL(request.url).pathname,
status: set.status ?? 200,
}),
);
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;
}
}
.get("/", () => "Hello world")
return { ok: true };
})
.post("/webhook/webflow", async ({ request, body, set }) => {
if (!WebflowService.Util.verifyWebflowSignature(request, body)) {
set.status = 400;
return "Invalid signature";
}
// CALCULATOR
const payload = body as Webflow.Webhook.EventPayload;
.post("/calculate", async ({ body, query }) => {
interface CalcRequest {
player: Player;
activityPerformances: ActivityPerformance[];
}
const { player, activityPerformances } = body as CalcRequest;
const output = {
levels: levelCalculator.calculate(player, activityPerformances),
};
switch (payload.triggerType) {
case Webflow.Webhook.Event.OrderCreated: {
const webflowOrder = payload.payload;
// 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(() => {});
// }
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,
})),
});
return output;
})
break;
}
}
// COMMERCE
// TODO: protect with api key/token
.group("/products", (app) =>
app
.group("/sync", (app) =>
app
.get("/", async ({}) => SyncService.state)
.post("/:printfulProductId?", async ({ params, set }) => {
if (SyncService.state.isSyncing) {
set.status = 409;
return { error: "Sync already in progress" };
}
const printfulProductId = params.printfulProductId
? +params.printfulProductId
: undefined;
await SyncService.sync(printfulProductId);
return { ok: true };
}),
)
.get("/:printfulProductId", async ({ params }) => {
const printfulProductId = +params.printfulProductId;
const printfulProduct =
await PrintfulService.Products.get(printfulProductId);
if (!printfulProduct) {
return new NotFoundError();
}
return { ok: true };
});
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);
+78 -78
View File
@@ -1,87 +1,87 @@
import {
type Levels,
DEFAULT_STANDARDS_DATA,
type Levels,
DEFAULT_STANDARDS_DATA,
} from "@blade-and-brawn/calculator";
import { Activity, clamp, Gender, range } from "@blade-and-brawn/calculator";
import { levenbergMarquardt as LM } from "ml-levenberg-marquardt";
for (const activity of Object.values(Activity)) {
const DATASET_MAX_LEVEL = 5;
const NEW_LEVEL_COUNT = 2;
const RATIO_CLAMP = 1.4;
const DATASET_MAX_LEVEL = 5;
const NEW_LEVEL_COUNT = 2;
const RATIO_CLAMP = 1.4;
function expDecayModel([A, B, C]: number[]) {
return (i: number) => A! * Math.exp(-B! * i) + C!;
}
for (const gender of Object.values(Gender)) {
const standardsByGender = DEFAULT_STANDARDS_DATA[activity].standards.filter(
(s) => s["metrics"].gender === gender,
);
const ages = [...new Set(standardsByGender.map((s) => s.metrics.age))];
for (const age of ages) {
const standards = standardsByGender.filter(
(s) => s.metrics.age === age,
);
const data = {
x: [] as number[],
y: [] as number[],
};
const isIncreasing =
standards[0]!.levels["2"]! > standards[0]!.levels["1"]!;
for (const i of range(DATASET_MAX_LEVEL).slice(0, -1)) {
const level = i + 1;
const progressionRatios: number[] = [];
for (const standard of standards) {
const curr = standard.levels[level]!;
const next = standard.levels[level]!;
progressionRatios.push(
isIncreasing ? next / curr : curr / next,
);
}
const sum = progressionRatios.reduce((p, c) => p + c, 0);
const progressionRatioAvg = sum / progressionRatios.length;
data.x.push(i);
data.y.push(progressionRatioAvg);
}
const fittedParams = LM(data, expDecayModel, {
initialValues: [0.4, 0.5, 1.1],
minValues: [0.0, 0.0, 1.02],
maxValues: [1.0, 2.0, 1.25],
maxIterations: 200,
});
const getDecayRatio = (i: number) =>
clamp(
expDecayModel(fittedParams.parameterValues)(i),
1,
RATIO_CLAMP,
);
// UPDATE THE DATA
for (const standard of standards) {
const newLevels: Levels = {};
let prev = standard.levels["1"]!;
for (const i of range(NEW_LEVEL_COUNT)) {
const level = i + 1;
const ratio = getDecayRatio(level - NEW_LEVEL_COUNT);
prev = isIncreasing ? prev / ratio : prev * ratio;
newLevels[level] = Math.round(prev);
}
const oldLevels = standard.levels as Levels;
for (const i of range(DATASET_MAX_LEVEL)) {
const level = i + 1;
newLevels[level + NEW_LEVEL_COUNT] = oldLevels[level]!;
}
standard.levels = newLevels;
}
function expDecayModel([A, B, C]: number[]) {
return (i: number) => A! * Math.exp(-B! * i) + C!;
}
for (const gender of Object.values(Gender)) {
const standardsByGender = DEFAULT_STANDARDS_DATA[
activity
].standards.filter((s) => s["metrics"].gender === gender);
const ages = [...new Set(standardsByGender.map((s) => s.metrics.age))];
for (const age of ages) {
const standards = standardsByGender.filter(
(s) => s.metrics.age === age,
);
const data = {
x: [] as number[],
y: [] as number[],
};
const isIncreasing =
standards[0]!.levels["2"]! > standards[0]!.levels["1"]!;
for (const i of range(DATASET_MAX_LEVEL).slice(0, -1)) {
const level = i + 1;
const progressionRatios: number[] = [];
for (const standard of standards) {
const curr = standard.levels[level]!;
const next = standard.levels[level]!;
progressionRatios.push(
isIncreasing ? next / curr : curr / next,
);
}
const sum = progressionRatios.reduce((p, c) => p + c, 0);
const progressionRatioAvg = sum / progressionRatios.length;
data.x.push(i);
data.y.push(progressionRatioAvg);
}
const fittedParams = LM(data, expDecayModel, {
initialValues: [0.4, 0.5, 1.1],
minValues: [0.0, 0.0, 1.02],
maxValues: [1.0, 2.0, 1.25],
maxIterations: 200,
});
const getDecayRatio = (i: number) =>
clamp(
expDecayModel(fittedParams.parameterValues)(i),
1,
RATIO_CLAMP,
);
// UPDATE THE DATA
for (const standard of standards) {
const newLevels: Levels = {};
let prev = standard.levels["1"]!;
for (const i of range(NEW_LEVEL_COUNT)) {
const level = i + 1;
const ratio = getDecayRatio(level - NEW_LEVEL_COUNT);
prev = isIncreasing ? prev / ratio : prev * ratio;
newLevels[level] = Math.round(prev);
}
const oldLevels = standard.levels as Levels;
for (const i of range(DATASET_MAX_LEVEL)) {
const level = i + 1;
newLevels[level + NEW_LEVEL_COUNT] = oldLevels[level]!;
}
standard.levels = newLevels;
}
}
}
}
}
+57 -53
View File
@@ -1,75 +1,79 @@
import { PrintfulService, SyncService, WebflowService } from "@blade-and-brawn/commerce";
import {
PrintfulService,
SyncService,
WebflowService,
} from "@blade-and-brawn/commerce";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
type Validation = {
isValid: boolean;
message: string;
isValid: boolean;
message: string;
};
const validateEquality = (wValue: any, pValue: any): Validation => {
const validation = {
isValid: true,
message: "Valid",
};
const validation = {
isValid: true,
message: "Valid",
};
if (wValue !== pValue) {
validation.isValid = false;
validation.message = `${wValue} !== ${pValue}`;
}
if (wValue !== pValue) {
validation.isValid = false;
validation.message = `${wValue} !== ${pValue}`;
}
return validation;
return validation;
};
const wProducts = await WebflowService.Products.getAll();
let invalidCount = 0;
for (const wProduct of wProducts) {
for (const sku of wProduct.skus ?? []) {
console.log("Webflow: " + sku.id, sku.fieldData.name);
for (const sku of wProduct.skus ?? []) {
console.log("Webflow: " + sku.id, sku.fieldData.name);
const pVariant = await PrintfulService.Products.Variants.get(
`@${sku.id}`,
);
if (pVariant) {
console.log("Printful: " + pVariant.id, pVariant.name);
const validations = [
// validateEquality(sku.fieldData.name, pVariant.name),
validateEquality(
sku.fieldData["sku-values"]?.["color"],
SyncService.findColorInProductName(pVariant.name),
),
validateEquality(
sku.fieldData["sku-values"]?.["size"],
pVariant.size,
),
validateEquality(
sku.fieldData.price.value,
Math.floor(+pVariant.retail_price * 100),
),
];
const pVariant = await PrintfulService.Products.Variants.get(
`@${sku.id}`,
);
if (pVariant) {
console.log("Printful: " + pVariant.id, pVariant.name);
const validations = [
// validateEquality(sku.fieldData.name, pVariant.name),
validateEquality(
sku.fieldData["sku-values"]?.["color"],
SyncService.findColorInProductName(pVariant.name),
),
validateEquality(
sku.fieldData["sku-values"]?.["size"],
pVariant.size,
),
validateEquality(
sku.fieldData.price.value,
Math.floor(+pVariant.retail_price * 100),
),
];
// if (!validateEquality(sku.fieldData.name, pVariant.name).isValid) {
// sku.fieldData.name = pVariant.name;
// await WebflowService.Products.Skus.update(wProduct.product.id, sku.id, sku);
// }
// if (!validateEquality(sku.fieldData.name, pVariant.name).isValid) {
// sku.fieldData.name = pVariant.name;
// await WebflowService.Products.Skus.update(wProduct.product.id, sku.id, sku);
// }
const errors = validations.filter((v) => !v.isValid);
if (errors.length) {
++invalidCount;
console.log("INVALID: ");
console.log(errors.map((e) => e.message).join(", "));
} else {
console.log("VALID");
}
} else {
++invalidCount;
console.log("INVALID");
console.log("Not found");
const errors = validations.filter((v) => !v.isValid);
if (errors.length) {
++invalidCount;
console.log("INVALID: ");
console.log(errors.map((e) => e.message).join(", "));
} else {
console.log("VALID");
}
} else {
++invalidCount;
console.log("INVALID");
console.log("Not found");
}
console.log();
}
console.log();
}
await sleep(3000);
await sleep(3000);
}
console.log("INVALID COUNT: " + invalidCount);
+1 -1
View File
@@ -1,3 +1,3 @@
{
"extends": "../../tsconfig.base.json",
"extends": "../../tsconfig.base.json"
}