breakout into monorepo
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# backend
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run index.ts
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "backend",
|
||||
"dependencies": {
|
||||
"@elysiajs/cors": "^1.4.1",
|
||||
"elysia": "^1.4.27",
|
||||
"ml-levenberg-marquardt": "^5.0.0",
|
||||
"zipcodes-us": "^1.1.2",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="],
|
||||
|
||||
"@elysiajs/cors": ["@elysiajs/cors@1.4.1", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-lQfad+F3r4mNwsxRKbXyJB8Jg43oAOXjRwn7sKUL6bcOW3KjUqUimTS+woNpO97efpzjtDE0tEjGk9DTw8lqTQ=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||
|
||||
"@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"elysia": ["elysia@1.4.27", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-2UlmNEjPJVA/WZVPYKy+KdsrfFwwNlqSBW1lHz6i2AHc75k7gV4Rhm01kFeotH7PDiHIX2G8X3KnRPc33SGVIg=="],
|
||||
|
||||
"exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"is-any-array": ["is-any-array@2.0.1", "", {}, "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ=="],
|
||||
|
||||
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
|
||||
|
||||
"ml-array-max": ["ml-array-max@1.2.4", "", { "dependencies": { "is-any-array": "^2.0.0" } }, "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ=="],
|
||||
|
||||
"ml-array-min": ["ml-array-min@1.2.3", "", { "dependencies": { "is-any-array": "^2.0.0" } }, "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q=="],
|
||||
|
||||
"ml-array-rescale": ["ml-array-rescale@1.3.7", "", { "dependencies": { "is-any-array": "^2.0.0", "ml-array-max": "^1.2.4", "ml-array-min": "^1.2.3" } }, "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ=="],
|
||||
|
||||
"ml-levenberg-marquardt": ["ml-levenberg-marquardt@5.0.0", "", { "dependencies": { "is-any-array": "^2.0.1", "ml-matrix": "^6.12.1" } }, "sha512-vCFoO2DyYKGZFp+KxofN2cAvSc/3gufJHj/oaDHA2nV+NZH509fD7m35zQR2ZXA8fepxpVrwgCAQ0aAIsV2WtA=="],
|
||||
|
||||
"ml-matrix": ["ml-matrix@6.12.1", "", { "dependencies": { "is-any-array": "^2.0.1", "ml-array-rescale": "^1.3.7" } }, "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||
|
||||
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
|
||||
|
||||
"token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"zipcodes-us": ["zipcodes-us@1.1.2", "", {}, "sha512-UmUTS0O0/IHb0hbxv5r6IfATvD2B7FKcJTLsSv/dENBRiNACFUrGmhPuVss3kAhuhEpPH6NwKHJ7/rGMNXq5yA=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
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 zipcodesUs from "zipcodes-us";
|
||||
|
||||
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",
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
.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();
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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;
|
||||
};
|
||||
|
||||
const validateEquality = (wValue: any, pValue: any): Validation => {
|
||||
const validation = {
|
||||
isValid: true,
|
||||
message: "Valid",
|
||||
};
|
||||
|
||||
if (wValue !== pValue) {
|
||||
validation.isValid = false;
|
||||
validation.message = `${wValue} !== ${pValue}`;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
// }
|
||||
|
||||
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();
|
||||
}
|
||||
await sleep(3000);
|
||||
}
|
||||
|
||||
console.log("INVALID COUNT: " + invalidCount);
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
}
|
||||
Reference in New Issue
Block a user