Overhaul logging
This commit is contained in:
@@ -3,12 +3,10 @@ import { formatSlug, type DeepPartial } from "./util/misc";
|
||||
import { WebflowClient } from "./webflow";
|
||||
import { PrintfulClient } from "./printful";
|
||||
import { redis } from "bun";
|
||||
import pino from "pino";
|
||||
|
||||
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 = {
|
||||
name: string;
|
||||
externalId: string;
|
||||
@@ -25,144 +23,149 @@ type SyncState = {
|
||||
};
|
||||
|
||||
export class ProductSyncer {
|
||||
private log: pino.Logger;
|
||||
|
||||
constructor(
|
||||
private printful: PrintfulClient,
|
||||
private webflow: WebflowClient,
|
||||
) { }
|
||||
log?: pino.Logger,
|
||||
) {
|
||||
this.log = (log ?? pino({ level: "silent" })).child({ component: "ProductSyncer" });
|
||||
}
|
||||
|
||||
async sync(printfulProductId?: number) {
|
||||
const syncStart = Date.now();
|
||||
try {
|
||||
if (printfulProductId) this.log.info({ printfulProductId }, "sync started");
|
||||
else this.log.info("sync started");
|
||||
|
||||
await this.setState({ isSyncing: true, syncingIds: [] });
|
||||
|
||||
// Populate products
|
||||
const mainPrintfulProducts: MainProduct[] = [];
|
||||
const webflowProducts: Webflow.Products.ProductAndSkus[] = [];
|
||||
this.log.debug("populating webflow products");
|
||||
const webflowProducts: Webflow.Products.ProductAndSkus[] = await this.webflow.Products.getAll();
|
||||
|
||||
this.log.debug("populating printful products");
|
||||
let printfulProducts = await this.printful.Products.getAll();
|
||||
if (printfulProductId) {
|
||||
const printfulProduct = printfulProducts.find(
|
||||
(p) => p.id === printfulProductId,
|
||||
);
|
||||
const printfulProduct = printfulProducts.find((p) => p.id === printfulProductId);
|
||||
if (printfulProduct) {
|
||||
const mainProductName = this.getMainProductName(
|
||||
const metaProductName = this.getMainProductName(
|
||||
printfulProduct.name,
|
||||
);
|
||||
printfulProducts = printfulProducts.filter((p) =>
|
||||
p.name.includes(mainProductName),
|
||||
p.name.includes(metaProductName),
|
||||
);
|
||||
}
|
||||
}
|
||||
webflowProducts.push(...(await this.webflow.Products.getAll()));
|
||||
|
||||
// Generate main printful products
|
||||
console.log("Generating main products...");
|
||||
this.log.info({ count: printfulProducts.length }, "generating meta printful products");
|
||||
const metaPrintfulProducts: MainProduct[] = [];
|
||||
for (const printfulProduct of printfulProducts) {
|
||||
const mainProductName = this.getMainProductName(
|
||||
printfulProduct.name,
|
||||
);
|
||||
|
||||
let mainPrintfulProduct = mainPrintfulProducts.find((mp) =>
|
||||
let metaPrintfulProduct = metaPrintfulProducts.find((mp) =>
|
||||
mp.name.includes(mainProductName),
|
||||
);
|
||||
if (!mainPrintfulProduct) {
|
||||
mainPrintfulProduct = {
|
||||
if (!metaPrintfulProduct) {
|
||||
metaPrintfulProduct = {
|
||||
name: mainProductName,
|
||||
variants: [],
|
||||
externalId: printfulProduct.external_id,
|
||||
colors: new Set(),
|
||||
};
|
||||
mainPrintfulProducts.push(mainPrintfulProduct);
|
||||
metaPrintfulProducts.push(metaPrintfulProduct);
|
||||
}
|
||||
|
||||
const productColor = this.findColorInProductName(
|
||||
printfulProduct.name,
|
||||
);
|
||||
mainPrintfulProduct.variants.push({
|
||||
metaPrintfulProduct.variants.push({
|
||||
color: productColor,
|
||||
product: (await this.printful.Products.get(printfulProduct.id))!,
|
||||
});
|
||||
mainPrintfulProduct.colors.add(productColor);
|
||||
metaPrintfulProduct.colors.add(productColor);
|
||||
}
|
||||
|
||||
// TODO: Validate main printful products
|
||||
|
||||
// Sync the main printful products
|
||||
console.log("Syncing main products...");
|
||||
for (const mainPrintfulProduct of mainPrintfulProducts) {
|
||||
for (const metaPrintfulProduct of metaPrintfulProducts) {
|
||||
this.log.info({ name: metaPrintfulProduct.name }, "syncing meta printful product");
|
||||
await this.setState({
|
||||
isSyncing: true,
|
||||
syncingIds: mainPrintfulProduct.variants.map(
|
||||
syncingIds: metaPrintfulProduct.variants.map(
|
||||
(v) => v.product.sync_product.id,
|
||||
),
|
||||
});
|
||||
|
||||
const foundColors = mainPrintfulProduct.colors;
|
||||
const foundColors = metaPrintfulProduct.colors;
|
||||
const foundSizes: Set<string> = new Set();
|
||||
|
||||
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) {
|
||||
// check if this variant size is in all other special variants
|
||||
this.log.debug(
|
||||
{ size: printfulVariant.size, color: printfulVariant.color },
|
||||
"generating webflow SKU from printful variant"
|
||||
);
|
||||
|
||||
// check if this variant size is in all other special variants, if its not then skip it
|
||||
let sizeIsInAllVariants = true;
|
||||
for (const specialVariant of mainPrintfulProduct.variants) {
|
||||
const sizes = specialVariant.product.sync_variants.map(
|
||||
(v) => v.size,
|
||||
);
|
||||
for (const metaVariant of metaPrintfulProduct.variants) {
|
||||
const sizes = metaVariant.product.sync_variants.map((v) => v.size);
|
||||
if (!sizes.includes(printfulVariant.size))
|
||||
sizeIsInAllVariants = false;
|
||||
}
|
||||
if (!sizeIsInAllVariants) continue;
|
||||
|
||||
if (sizeIsInAllVariants) {
|
||||
foundSizes.add(printfulVariant.size);
|
||||
foundSizes.add(printfulVariant.size);
|
||||
|
||||
webflowSkus.push({
|
||||
id: printfulVariant.external_id,
|
||||
fieldData: {
|
||||
name: printfulVariant.name,
|
||||
slug: formatSlug(printfulVariant.name),
|
||||
"sku-values": {
|
||||
color: specialVariant.color,
|
||||
size: printfulVariant.size,
|
||||
},
|
||||
price: {
|
||||
value: Math.floor(
|
||||
+printfulVariant.retail_price * 100,
|
||||
),
|
||||
unit: printfulVariant.currency,
|
||||
currency: printfulVariant.currency,
|
||||
},
|
||||
"main-image": PrintfulClient.Util.getVariantMainImage(
|
||||
printfulVariant,
|
||||
),
|
||||
webflowSkus.push({
|
||||
id: printfulVariant.external_id,
|
||||
fieldData: {
|
||||
name: printfulVariant.name,
|
||||
slug: formatSlug(printfulVariant.name),
|
||||
"sku-values": {
|
||||
color: specialVariant.color,
|
||||
size: printfulVariant.size,
|
||||
},
|
||||
});
|
||||
}
|
||||
price: {
|
||||
value: Math.floor(
|
||||
+printfulVariant.retail_price * 100,
|
||||
),
|
||||
unit: printfulVariant.currency,
|
||||
currency: printfulVariant.currency,
|
||||
},
|
||||
"main-image": PrintfulClient.Util.getVariantMainImage(
|
||||
printfulVariant,
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// SYNC PRODUCT DATA
|
||||
let existingWebflowProduct = webflowProducts.find(
|
||||
(webflowProduct) =>
|
||||
webflowProduct.product.id ===
|
||||
mainPrintfulProduct.externalId.split("-")[0],
|
||||
(webflowProduct) => webflowProduct.product.id === metaPrintfulProduct.externalId.split("-")[0],
|
||||
);
|
||||
if (existingWebflowProduct) {
|
||||
// SYNC PRODUCT UPDATE
|
||||
this.log.info("existing webflow product found, updating it");
|
||||
|
||||
// do not update images
|
||||
for (const webflowSku of webflowSkus) {
|
||||
for (const webflowSku of webflowSkus)
|
||||
delete webflowSku.fieldData?.["main-image"];
|
||||
}
|
||||
|
||||
const webflowProductId =
|
||||
mainPrintfulProduct.externalId.split("-")[0];
|
||||
if (!webflowProductId)
|
||||
throw new Error("Malformed printful product ID");
|
||||
const webflowProductId = metaPrintfulProduct.externalId.split("-")[0];
|
||||
if (!webflowProductId) throw new Error("Malformed printful product ID");
|
||||
|
||||
await this.webflow.Products.update(webflowProductId, {
|
||||
product: {
|
||||
fieldData: {
|
||||
name: mainPrintfulProduct.name,
|
||||
slug: formatSlug(mainPrintfulProduct.name),
|
||||
name: metaPrintfulProduct.name,
|
||||
slug: formatSlug(metaPrintfulProduct.name),
|
||||
shippable: true,
|
||||
"tax-category": "standard-taxable",
|
||||
"sku-properties": [
|
||||
@@ -209,11 +212,13 @@ export class ProductSyncer {
|
||||
}
|
||||
} else {
|
||||
// SYNC PRODUCT CREATE
|
||||
this.log.info("existing webflow product not found, creating it");
|
||||
|
||||
const webflowProductId = await this.webflow.Products.create({
|
||||
product: {
|
||||
fieldData: {
|
||||
name: mainPrintfulProduct.name,
|
||||
slug: formatSlug(mainPrintfulProduct.name),
|
||||
name: metaPrintfulProduct.name,
|
||||
slug: formatSlug(metaPrintfulProduct.name),
|
||||
shippable: true,
|
||||
"tax-category": "standard-taxable",
|
||||
"sku-properties": [
|
||||
@@ -249,12 +254,11 @@ export class ProductSyncer {
|
||||
|
||||
existingWebflowProduct = await this.webflow.Products.get(webflowProductId);
|
||||
if (!existingWebflowProduct)
|
||||
throw new Error("Webflow product missing after create");
|
||||
throw new Error("webflow product missing after create");
|
||||
|
||||
for (const specialVariant of mainPrintfulProduct.variants) {
|
||||
const newPrintfulVariants: DeepPartial<Printful.Products.SyncVariant>[] =
|
||||
[];
|
||||
for (const printfulVariant of specialVariant.product.sync_variants) {
|
||||
for (const metaVariant of metaPrintfulProduct.variants) {
|
||||
const newPrintfulVariants: DeepPartial<Printful.Products.SyncVariant>[] = [];
|
||||
for (const printfulVariant of metaVariant.product.sync_variants) {
|
||||
const associatedWebflowSku = existingWebflowProduct.skus.find(
|
||||
(sku) =>
|
||||
sku.fieldData["sku-values"]?.["color"] === printfulVariant.color &&
|
||||
@@ -269,12 +273,13 @@ export class ProductSyncer {
|
||||
}
|
||||
|
||||
await sleep(10000);
|
||||
this.log.info({ printfulProduct: metaVariant.product.sync_product.id }, "updating printful product");
|
||||
await this.printful.Products.update(
|
||||
specialVariant.product.sync_product.id,
|
||||
metaVariant.product.sync_product.id,
|
||||
{
|
||||
sync_product: {
|
||||
id: specialVariant.product.sync_product.id,
|
||||
external_id: `${webflowProductId}-${specialVariant.color}`,
|
||||
id: metaVariant.product.sync_product.id,
|
||||
external_id: `${webflowProductId}-${metaVariant.color}`,
|
||||
},
|
||||
sync_variants: newPrintfulVariants,
|
||||
},
|
||||
@@ -282,13 +287,13 @@ export class ProductSyncer {
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Done");
|
||||
}
|
||||
finally {
|
||||
|
||||
this.log.info({ durationMs: Date.now() - syncStart }, "sync complete");
|
||||
} finally {
|
||||
try {
|
||||
await this.setState({ isSyncing: false, syncingIds: [] });
|
||||
} catch {
|
||||
console.error("Failed to reset sync state");
|
||||
this.log.error("failed to reset sync state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user