Overhaul logging
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"@elysiajs/cors": "^1.4.2",
|
"@elysiajs/cors": "^1.4.2",
|
||||||
"elysia": "^1.4.29",
|
"elysia": "^1.4.29",
|
||||||
"ml-levenberg-marquardt": "^5.0.1",
|
"ml-levenberg-marquardt": "^5.0.1",
|
||||||
|
"pino": "^10.3.1",
|
||||||
"zipcodes-us": "^1.1.3",
|
"zipcodes-us": "^1.1.3",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -4,11 +4,18 @@ function requireEnv(key: string): string {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function optionEnv(key: string, fallback: string): string {
|
||||||
|
const val = Bun.env[key];
|
||||||
|
if (!val) return fallback;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
export const env = {
|
export const env = {
|
||||||
PRINTFUL_AUTH: requireEnv("PRINTFUL_AUTH"),
|
PRINTFUL_AUTH: requireEnv("PRINTFUL_AUTH"),
|
||||||
PRINTFUL_STORE_ID: requireEnv("PRINTFUL_STORE_ID"),
|
PRINTFUL_STORE_ID: requireEnv("PRINTFUL_STORE_ID"),
|
||||||
WEBFLOW_SITE_ID: requireEnv("WEBFLOW_SITE_ID"),
|
WEBFLOW_SITE_ID: requireEnv("WEBFLOW_SITE_ID"),
|
||||||
WEBFLOW_COLLECTION_ID: requireEnv("WEBFLOW_COLLECTION_ID"),
|
WEBFLOW_COLLECTION_ID: requireEnv("WEBFLOW_COLLECTION_ID"),
|
||||||
WEBFLOW_AUTH: requireEnv("WEBFLOW_AUTH"),
|
WEBFLOW_AUTH: requireEnv("WEBFLOW_AUTH"),
|
||||||
WEBFLOW_WEBHOOK_SECRET: requireEnv("WEBFLOW_WEBHOOK_SECRET")
|
WEBFLOW_WEBHOOK_SECRET: requireEnv("WEBFLOW_WEBHOOK_SECRET"),
|
||||||
|
LOG_LEVEL: optionEnv("LOG_LEVEL", "info")
|
||||||
};
|
};
|
||||||
|
|||||||
+36
-29
@@ -21,8 +21,11 @@ import {
|
|||||||
} from "@blade-and-brawn/commerce";
|
} from "@blade-and-brawn/commerce";
|
||||||
import zipcodesUs from "zipcodes-us";
|
import zipcodesUs from "zipcodes-us";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import pino from "pino";
|
||||||
import { env } from "./env";
|
import { env } from "./env";
|
||||||
|
|
||||||
|
const log = pino({ level: env.LOG_LEVEL });
|
||||||
|
|
||||||
// SERVICES
|
// SERVICES
|
||||||
// -----------
|
// -----------
|
||||||
const levelCalculator = new LevelCalculator(
|
const levelCalculator = new LevelCalculator(
|
||||||
@@ -41,11 +44,15 @@ const webflow = new WebflowClient({
|
|||||||
webhookSecret: env.WEBFLOW_WEBHOOK_SECRET,
|
webhookSecret: env.WEBFLOW_WEBHOOK_SECRET,
|
||||||
});
|
});
|
||||||
|
|
||||||
const productSyncer = new ProductSyncer(printful, webflow);
|
const productSyncer = new ProductSyncer(printful, webflow, log);
|
||||||
|
|
||||||
// ELYSIA
|
// ELYSIA
|
||||||
// -----------
|
// -----------
|
||||||
export const app = new Elysia()
|
export const app = new Elysia()
|
||||||
|
.derive(() => ({
|
||||||
|
startTime: Date.now(),
|
||||||
|
requestId: crypto.randomUUID(),
|
||||||
|
}))
|
||||||
.use(
|
.use(
|
||||||
cors({
|
cors({
|
||||||
origin: [
|
origin: [
|
||||||
@@ -71,27 +78,29 @@ export const app = new Elysia()
|
|||||||
WebflowError,
|
WebflowError,
|
||||||
})
|
})
|
||||||
|
|
||||||
.onError(({ code, error }) => {
|
.onError(({ code, error, startTime, requestId }) => {
|
||||||
|
const duration = Date.now() - (startTime ?? Date.now());
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case "PrintfulError":
|
case "PrintfulError":
|
||||||
case "WebflowError":
|
case "WebflowError":
|
||||||
console.error(error.message, error.status, error.payload);
|
log.error(
|
||||||
|
{ requestId, duration, upstreamStatus: error.status, payload: error.payload },
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
return status(502, { error: error.message });
|
return status(502, { error: error.message });
|
||||||
default:
|
default:
|
||||||
console.error(error);
|
log.error({ requestId, duration, err: error }, "unhandled error");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.onAfterHandle(({ request, set }) => {
|
.onAfterHandle(({ request, set, startTime, requestId }) => {
|
||||||
console.log(
|
log.info({
|
||||||
JSON.stringify({
|
requestId,
|
||||||
lvl: "info",
|
|
||||||
msg: "req",
|
|
||||||
method: request.method,
|
method: request.method,
|
||||||
path: new URL(request.url).pathname,
|
path: new URL(request.url).pathname,
|
||||||
status: set.status ?? 200,
|
status: set.status ?? 200,
|
||||||
}),
|
duration: Date.now() - startTime,
|
||||||
);
|
}, "request");
|
||||||
})
|
})
|
||||||
|
|
||||||
.get("/", () => ({ status: "ok" }))
|
.get("/", () => ({ status: "ok" }))
|
||||||
@@ -148,18 +157,14 @@ export const app = new Elysia()
|
|||||||
await productSyncer.sync(params.printfulProductId);
|
await productSyncer.sync(params.printfulProductId);
|
||||||
}, { params: z.object({ printfulProductId: z.coerce.number() }) }),
|
}, { params: z.object({ printfulProductId: z.coerce.number() }) }),
|
||||||
)
|
)
|
||||||
// TODO: add schema validation
|
|
||||||
.get("/:printfulProductId", async ({ params }) => {
|
.get("/:printfulProductId", async ({ params }) => {
|
||||||
const printfulProduct =
|
const printfulProduct = await printful.Products.get(params.printfulProductId);
|
||||||
await printful.Products.get(params.printfulProductId);
|
|
||||||
if (!printfulProduct) throw new NotFoundError("Missing printful product");
|
if (!printfulProduct) throw new NotFoundError("Missing printful product");
|
||||||
|
|
||||||
const webflowProductId =
|
const webflowProductId = printfulProduct.sync_product.external_id.split("-")[0];
|
||||||
printfulProduct.sync_product.external_id.split("-")[0];
|
|
||||||
if (!webflowProductId) throw new NotFoundError("Missing webflow product ID");
|
if (!webflowProductId) throw new NotFoundError("Missing webflow product ID");
|
||||||
|
|
||||||
const webflowProduct =
|
const webflowProduct = await webflow.Products.get(webflowProductId);
|
||||||
await webflow.Products.get(webflowProductId);
|
|
||||||
|
|
||||||
return { printfulProduct, webflowProduct };
|
return { printfulProduct, webflowProduct };
|
||||||
}, {
|
}, {
|
||||||
@@ -174,18 +179,19 @@ export const app = new Elysia()
|
|||||||
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;
|
||||||
|
log.info({ productId: printfulProduct.id }, "printful webhook: product updated");
|
||||||
await productSyncer.sync(printfulProduct.id);
|
await productSyncer.sync(printfulProduct.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Printful.Webhook.Event.ProductDeleted: {
|
case Printful.Webhook.Event.ProductDeleted: {
|
||||||
await webflow.Products.remove(
|
log.info({ externalId: payload.data.sync_product.external_id }, "printful webhook: product deleted");
|
||||||
payload.data.sync_product.external_id,
|
await webflow.Products.remove(payload.data.sync_product.external_id);
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Printful.Webhook.Event.PackageShipped: {
|
case Printful.Webhook.Event.PackageShipped: {
|
||||||
const webflowOrderId = payload.data.order.external_id;
|
const webflowOrderId = payload.data.order.external_id;
|
||||||
const shipInfo = payload.data.shipment;
|
const shipInfo = payload.data.shipment;
|
||||||
|
log.info({ webflowOrderId, carrier: shipInfo.carrier, tracking: shipInfo.tracking_number }, "printful webhook: package shipped");
|
||||||
|
|
||||||
await webflow.Orders.update(webflowOrderId, {
|
await webflow.Orders.update(webflowOrderId, {
|
||||||
shippingTrackingURL: shipInfo.tracking_url,
|
shippingTrackingURL: shipInfo.tracking_url,
|
||||||
@@ -197,6 +203,8 @@ export const app = new Elysia()
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
log.warn({ type: (payload as any).type }, "printful webhook: unhandled event type");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.post("/webhook/webflow", async ({ request, body }) => {
|
.post("/webhook/webflow", async ({ request, body }) => {
|
||||||
@@ -208,6 +216,7 @@ export const app = new Elysia()
|
|||||||
switch (payload.triggerType) {
|
switch (payload.triggerType) {
|
||||||
case Webflow.Webhook.Event.OrderCreated: {
|
case Webflow.Webhook.Event.OrderCreated: {
|
||||||
const webflowOrder = payload.payload;
|
const webflowOrder = payload.payload;
|
||||||
|
log.info({ orderId: webflowOrder.orderId }, "webflow webhook: order created");
|
||||||
|
|
||||||
await printful.Orders.create({
|
await printful.Orders.create({
|
||||||
external_id: webflowOrder.orderId,
|
external_id: webflowOrder.orderId,
|
||||||
@@ -219,24 +228,22 @@ export const app = new Elysia()
|
|||||||
address2: webflowOrder.shippingAddress.line2,
|
address2: webflowOrder.shippingAddress.line2,
|
||||||
city: webflowOrder.shippingAddress.city,
|
city: webflowOrder.shippingAddress.city,
|
||||||
state_code: zipcodesUs.find(
|
state_code: zipcodesUs.find(
|
||||||
webflowOrder.shippingAddress.postalCode.split(
|
webflowOrder.shippingAddress.postalCode.split("-")[0] ?? "",
|
||||||
"-",
|
|
||||||
)[0] ?? "",
|
|
||||||
).stateCode,
|
).stateCode,
|
||||||
country_code: webflowOrder.shippingAddress.country,
|
country_code: webflowOrder.shippingAddress.country,
|
||||||
zip: webflowOrder.shippingAddress.postalCode,
|
zip: webflowOrder.shippingAddress.postalCode,
|
||||||
},
|
},
|
||||||
items: webflowOrder.purchasedItems.map(
|
items: webflowOrder.purchasedItems.map((webflowOrderSku) => ({
|
||||||
(webflowOrderSku) => ({
|
|
||||||
external_variant_id: webflowOrderSku.variantId,
|
external_variant_id: webflowOrderSku.variantId,
|
||||||
quantity: webflowOrderSku.count,
|
quantity: webflowOrderSku.count,
|
||||||
}),
|
})),
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
log.warn({ triggerType: (payload as any).triggerType }, "webflow webhook: unhandled event type");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(3000);
|
app.listen(3000, () => log.info({ port: 3000 }, "server started"));
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@elysiajs/cors": "^1.4.2",
|
"@elysiajs/cors": "^1.4.2",
|
||||||
"elysia": "^1.4.29",
|
"elysia": "^1.4.29",
|
||||||
"ml-levenberg-marquardt": "^5.0.1",
|
"ml-levenberg-marquardt": "^5.0.1",
|
||||||
|
"pino": "^10.3.1",
|
||||||
"zipcodes-us": "^1.1.3",
|
"zipcodes-us": "^1.1.3",
|
||||||
"zod": "^4.4.3",
|
"zod": "^4.4.3",
|
||||||
},
|
},
|
||||||
@@ -56,6 +57,9 @@
|
|||||||
"packages/commerce": {
|
"packages/commerce": {
|
||||||
"name": "@blade-and-brawn/commerce",
|
"name": "@blade-and-brawn/commerce",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"pino": "^10.3.1",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"packages/domain": {
|
"packages/domain": {
|
||||||
"name": "@blade-and-brawn/domain",
|
"name": "@blade-and-brawn/domain",
|
||||||
@@ -143,6 +147,8 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
||||||
@@ -257,6 +263,8 @@
|
|||||||
|
|
||||||
"aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
|
"aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
|
||||||
|
|
||||||
|
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||||
@@ -363,26 +371,46 @@
|
|||||||
|
|
||||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||||
|
|
||||||
|
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
||||||
|
|
||||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
|
"pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
|
||||||
|
|
||||||
|
"pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
|
||||||
|
|
||||||
|
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
|
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||||
|
|
||||||
|
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
||||||
|
|
||||||
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
|
||||||
|
|
||||||
|
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||||
|
|
||||||
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
|
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||||
|
|
||||||
|
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||||
|
|
||||||
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
|
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
|
||||||
|
|
||||||
"svelte": ["svelte@5.56.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.11", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA=="],
|
"svelte": ["svelte@5.56.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.11", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA=="],
|
||||||
@@ -395,6 +423,8 @@
|
|||||||
|
|
||||||
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||||
|
|
||||||
|
"thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="],
|
||||||
|
|
||||||
"tiny-glob": ["tiny-glob@0.2.9", "", { "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg=="],
|
"tiny-glob": ["tiny-glob@0.2.9", "", { "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
@@ -432,5 +462,7 @@
|
|||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pino": "^10.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import { formatSlug, type DeepPartial } from "./util/misc";
|
|||||||
import { WebflowClient } from "./webflow";
|
import { WebflowClient } from "./webflow";
|
||||||
import { PrintfulClient } from "./printful";
|
import { PrintfulClient } from "./printful";
|
||||||
import { redis } from "bun";
|
import { redis } from "bun";
|
||||||
|
import pino from "pino";
|
||||||
|
|
||||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
export const R2_WORKER_URL = "https://r2-worker.xominus.workers.dev";
|
|
||||||
export const WEBSITE_MEDIA_URL = "https://website-media.bladeandbrawn.com";
|
|
||||||
|
|
||||||
type MainProduct = {
|
type MainProduct = {
|
||||||
name: string;
|
name: string;
|
||||||
externalId: string;
|
externalId: string;
|
||||||
@@ -25,94 +23,103 @@ type SyncState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class ProductSyncer {
|
export class ProductSyncer {
|
||||||
|
private log: pino.Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private printful: PrintfulClient,
|
private printful: PrintfulClient,
|
||||||
private webflow: WebflowClient,
|
private webflow: WebflowClient,
|
||||||
) { }
|
log?: pino.Logger,
|
||||||
|
) {
|
||||||
|
this.log = (log ?? pino({ level: "silent" })).child({ component: "ProductSyncer" });
|
||||||
|
}
|
||||||
|
|
||||||
async sync(printfulProductId?: number) {
|
async sync(printfulProductId?: number) {
|
||||||
|
const syncStart = Date.now();
|
||||||
try {
|
try {
|
||||||
|
if (printfulProductId) this.log.info({ printfulProductId }, "sync started");
|
||||||
|
else this.log.info("sync started");
|
||||||
|
|
||||||
await this.setState({ isSyncing: true, syncingIds: [] });
|
await this.setState({ isSyncing: true, syncingIds: [] });
|
||||||
|
|
||||||
// Populate products
|
this.log.debug("populating webflow products");
|
||||||
const mainPrintfulProducts: MainProduct[] = [];
|
const webflowProducts: Webflow.Products.ProductAndSkus[] = await this.webflow.Products.getAll();
|
||||||
const webflowProducts: Webflow.Products.ProductAndSkus[] = [];
|
|
||||||
|
this.log.debug("populating printful products");
|
||||||
let printfulProducts = await this.printful.Products.getAll();
|
let printfulProducts = await this.printful.Products.getAll();
|
||||||
if (printfulProductId) {
|
if (printfulProductId) {
|
||||||
const printfulProduct = printfulProducts.find(
|
const printfulProduct = printfulProducts.find((p) => p.id === printfulProductId);
|
||||||
(p) => p.id === printfulProductId,
|
|
||||||
);
|
|
||||||
if (printfulProduct) {
|
if (printfulProduct) {
|
||||||
const mainProductName = this.getMainProductName(
|
const metaProductName = this.getMainProductName(
|
||||||
printfulProduct.name,
|
printfulProduct.name,
|
||||||
);
|
);
|
||||||
printfulProducts = printfulProducts.filter((p) =>
|
printfulProducts = printfulProducts.filter((p) =>
|
||||||
p.name.includes(mainProductName),
|
p.name.includes(metaProductName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
webflowProducts.push(...(await this.webflow.Products.getAll()));
|
|
||||||
|
|
||||||
// Generate main printful products
|
this.log.info({ count: printfulProducts.length }, "generating meta printful products");
|
||||||
console.log("Generating main products...");
|
const metaPrintfulProducts: MainProduct[] = [];
|
||||||
for (const printfulProduct of printfulProducts) {
|
for (const printfulProduct of printfulProducts) {
|
||||||
const mainProductName = this.getMainProductName(
|
const mainProductName = this.getMainProductName(
|
||||||
printfulProduct.name,
|
printfulProduct.name,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mainPrintfulProduct = mainPrintfulProducts.find((mp) =>
|
let metaPrintfulProduct = metaPrintfulProducts.find((mp) =>
|
||||||
mp.name.includes(mainProductName),
|
mp.name.includes(mainProductName),
|
||||||
);
|
);
|
||||||
if (!mainPrintfulProduct) {
|
if (!metaPrintfulProduct) {
|
||||||
mainPrintfulProduct = {
|
metaPrintfulProduct = {
|
||||||
name: mainProductName,
|
name: mainProductName,
|
||||||
variants: [],
|
variants: [],
|
||||||
externalId: printfulProduct.external_id,
|
externalId: printfulProduct.external_id,
|
||||||
colors: new Set(),
|
colors: new Set(),
|
||||||
};
|
};
|
||||||
mainPrintfulProducts.push(mainPrintfulProduct);
|
metaPrintfulProducts.push(metaPrintfulProduct);
|
||||||
}
|
}
|
||||||
|
|
||||||
const productColor = this.findColorInProductName(
|
const productColor = this.findColorInProductName(
|
||||||
printfulProduct.name,
|
printfulProduct.name,
|
||||||
);
|
);
|
||||||
mainPrintfulProduct.variants.push({
|
metaPrintfulProduct.variants.push({
|
||||||
color: productColor,
|
color: productColor,
|
||||||
product: (await this.printful.Products.get(printfulProduct.id))!,
|
product: (await this.printful.Products.get(printfulProduct.id))!,
|
||||||
});
|
});
|
||||||
mainPrintfulProduct.colors.add(productColor);
|
metaPrintfulProduct.colors.add(productColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate main printful products
|
// TODO: Validate main printful products
|
||||||
|
|
||||||
// Sync the main printful products
|
for (const metaPrintfulProduct of metaPrintfulProducts) {
|
||||||
console.log("Syncing main products...");
|
this.log.info({ name: metaPrintfulProduct.name }, "syncing meta printful product");
|
||||||
for (const mainPrintfulProduct of mainPrintfulProducts) {
|
|
||||||
await this.setState({
|
await this.setState({
|
||||||
isSyncing: true,
|
isSyncing: true,
|
||||||
syncingIds: mainPrintfulProduct.variants.map(
|
syncingIds: metaPrintfulProduct.variants.map(
|
||||||
(v) => v.product.sync_product.id,
|
(v) => v.product.sync_product.id,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const foundColors = mainPrintfulProduct.colors;
|
const foundColors = metaPrintfulProduct.colors;
|
||||||
const foundSizes: Set<string> = new Set();
|
const foundSizes: Set<string> = new Set();
|
||||||
|
|
||||||
const webflowSkus: DeepPartial<Webflow.Products.Skus.Sku>[] = [];
|
const webflowSkus: DeepPartial<Webflow.Products.Skus.Sku>[] = [];
|
||||||
|
|
||||||
for (const specialVariant of mainPrintfulProduct.variants) {
|
for (const specialVariant of metaPrintfulProduct.variants) {
|
||||||
for (const printfulVariant of specialVariant.product.sync_variants) {
|
for (const printfulVariant of specialVariant.product.sync_variants) {
|
||||||
// check if this variant size is in all other special variants
|
this.log.debug(
|
||||||
let sizeIsInAllVariants = true;
|
{ size: printfulVariant.size, color: printfulVariant.color },
|
||||||
for (const specialVariant of mainPrintfulProduct.variants) {
|
"generating webflow SKU from printful variant"
|
||||||
const sizes = specialVariant.product.sync_variants.map(
|
|
||||||
(v) => v.size,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// check if this variant size is in all other special variants, if its not then skip it
|
||||||
|
let sizeIsInAllVariants = true;
|
||||||
|
for (const metaVariant of metaPrintfulProduct.variants) {
|
||||||
|
const sizes = metaVariant.product.sync_variants.map((v) => v.size);
|
||||||
if (!sizes.includes(printfulVariant.size))
|
if (!sizes.includes(printfulVariant.size))
|
||||||
sizeIsInAllVariants = false;
|
sizeIsInAllVariants = false;
|
||||||
}
|
}
|
||||||
|
if (!sizeIsInAllVariants) continue;
|
||||||
|
|
||||||
if (sizeIsInAllVariants) {
|
|
||||||
foundSizes.add(printfulVariant.size);
|
foundSizes.add(printfulVariant.size);
|
||||||
|
|
||||||
webflowSkus.push({
|
webflowSkus.push({
|
||||||
@@ -138,31 +145,27 @@ export class ProductSyncer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// SYNC PRODUCT DATA
|
// SYNC PRODUCT DATA
|
||||||
let existingWebflowProduct = webflowProducts.find(
|
let existingWebflowProduct = webflowProducts.find(
|
||||||
(webflowProduct) =>
|
(webflowProduct) => webflowProduct.product.id === metaPrintfulProduct.externalId.split("-")[0],
|
||||||
webflowProduct.product.id ===
|
|
||||||
mainPrintfulProduct.externalId.split("-")[0],
|
|
||||||
);
|
);
|
||||||
if (existingWebflowProduct) {
|
if (existingWebflowProduct) {
|
||||||
// SYNC PRODUCT UPDATE
|
// SYNC PRODUCT UPDATE
|
||||||
|
this.log.info("existing webflow product found, updating it");
|
||||||
|
|
||||||
// do not update images
|
// do not update images
|
||||||
for (const webflowSku of webflowSkus) {
|
for (const webflowSku of webflowSkus)
|
||||||
delete webflowSku.fieldData?.["main-image"];
|
delete webflowSku.fieldData?.["main-image"];
|
||||||
}
|
|
||||||
|
|
||||||
const webflowProductId =
|
const webflowProductId = metaPrintfulProduct.externalId.split("-")[0];
|
||||||
mainPrintfulProduct.externalId.split("-")[0];
|
if (!webflowProductId) throw new Error("Malformed printful product ID");
|
||||||
if (!webflowProductId)
|
|
||||||
throw new Error("Malformed printful product ID");
|
|
||||||
await this.webflow.Products.update(webflowProductId, {
|
await this.webflow.Products.update(webflowProductId, {
|
||||||
product: {
|
product: {
|
||||||
fieldData: {
|
fieldData: {
|
||||||
name: mainPrintfulProduct.name,
|
name: metaPrintfulProduct.name,
|
||||||
slug: formatSlug(mainPrintfulProduct.name),
|
slug: formatSlug(metaPrintfulProduct.name),
|
||||||
shippable: true,
|
shippable: true,
|
||||||
"tax-category": "standard-taxable",
|
"tax-category": "standard-taxable",
|
||||||
"sku-properties": [
|
"sku-properties": [
|
||||||
@@ -209,11 +212,13 @@ export class ProductSyncer {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// SYNC PRODUCT CREATE
|
// SYNC PRODUCT CREATE
|
||||||
|
this.log.info("existing webflow product not found, creating it");
|
||||||
|
|
||||||
const webflowProductId = await this.webflow.Products.create({
|
const webflowProductId = await this.webflow.Products.create({
|
||||||
product: {
|
product: {
|
||||||
fieldData: {
|
fieldData: {
|
||||||
name: mainPrintfulProduct.name,
|
name: metaPrintfulProduct.name,
|
||||||
slug: formatSlug(mainPrintfulProduct.name),
|
slug: formatSlug(metaPrintfulProduct.name),
|
||||||
shippable: true,
|
shippable: true,
|
||||||
"tax-category": "standard-taxable",
|
"tax-category": "standard-taxable",
|
||||||
"sku-properties": [
|
"sku-properties": [
|
||||||
@@ -249,12 +254,11 @@ export class ProductSyncer {
|
|||||||
|
|
||||||
existingWebflowProduct = await this.webflow.Products.get(webflowProductId);
|
existingWebflowProduct = await this.webflow.Products.get(webflowProductId);
|
||||||
if (!existingWebflowProduct)
|
if (!existingWebflowProduct)
|
||||||
throw new Error("Webflow product missing after create");
|
throw new Error("webflow product missing after create");
|
||||||
|
|
||||||
for (const specialVariant of mainPrintfulProduct.variants) {
|
for (const metaVariant of metaPrintfulProduct.variants) {
|
||||||
const newPrintfulVariants: DeepPartial<Printful.Products.SyncVariant>[] =
|
const newPrintfulVariants: DeepPartial<Printful.Products.SyncVariant>[] = [];
|
||||||
[];
|
for (const printfulVariant of metaVariant.product.sync_variants) {
|
||||||
for (const printfulVariant of specialVariant.product.sync_variants) {
|
|
||||||
const associatedWebflowSku = existingWebflowProduct.skus.find(
|
const associatedWebflowSku = existingWebflowProduct.skus.find(
|
||||||
(sku) =>
|
(sku) =>
|
||||||
sku.fieldData["sku-values"]?.["color"] === printfulVariant.color &&
|
sku.fieldData["sku-values"]?.["color"] === printfulVariant.color &&
|
||||||
@@ -269,12 +273,13 @@ export class ProductSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await sleep(10000);
|
await sleep(10000);
|
||||||
|
this.log.info({ printfulProduct: metaVariant.product.sync_product.id }, "updating printful product");
|
||||||
await this.printful.Products.update(
|
await this.printful.Products.update(
|
||||||
specialVariant.product.sync_product.id,
|
metaVariant.product.sync_product.id,
|
||||||
{
|
{
|
||||||
sync_product: {
|
sync_product: {
|
||||||
id: specialVariant.product.sync_product.id,
|
id: metaVariant.product.sync_product.id,
|
||||||
external_id: `${webflowProductId}-${specialVariant.color}`,
|
external_id: `${webflowProductId}-${metaVariant.color}`,
|
||||||
},
|
},
|
||||||
sync_variants: newPrintfulVariants,
|
sync_variants: newPrintfulVariants,
|
||||||
},
|
},
|
||||||
@@ -282,13 +287,13 @@ export class ProductSyncer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Done");
|
|
||||||
}
|
this.log.info({ durationMs: Date.now() - syncStart }, "sync complete");
|
||||||
finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await this.setState({ isSyncing: false, syncingIds: [] });
|
await this.setState({ isSyncing: false, syncingIds: [] });
|
||||||
} catch {
|
} catch {
|
||||||
console.error("Failed to reset sync state");
|
this.log.error("failed to reset sync state");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user