From 5bae0f067d87561c2d130914a85abf2a2adb1366 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Tue, 23 Jun 2026 14:24:01 -0400 Subject: [PATCH] Refactor to store sync state in redis --- apps/api/src/server.ts | 8 ++-- packages/commerce/src/sync.ts | 70 ++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 8071189..7116478 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -105,10 +105,11 @@ export const app = new Elysia() .group("/sync", (app) => app // Sync status - .get("/", async ({ }) => SyncService.state) + .get("/", async ({ }) => await SyncService.getState()) // Full sync .post("/", async ({ }) => { - if (SyncService.state.isSyncing) + const syncState = await SyncService.getState(); + if (syncState.isSyncing) return status(409, { error: "Sync already in progress" }); await SyncService.sync(); @@ -116,7 +117,8 @@ export const app = new Elysia() }) // Per-product sync .post("/:printfulProductId", async ({ params }) => { - if (SyncService.state.isSyncing) + const syncState = await SyncService.getState(); + if (syncState.isSyncing) return status(409, { error: "Sync already in progress" }); await SyncService.sync(params.printfulProductId); diff --git a/packages/commerce/src/sync.ts b/packages/commerce/src/sync.ts index 4054e15..9d8e32b 100644 --- a/packages/commerce/src/sync.ts +++ b/packages/commerce/src/sync.ts @@ -2,6 +2,7 @@ import type { Printful, Webflow } from "./util/types"; import { FetchError, formatSlug, type DeepPartial } from "./util/misc"; import { WebflowService } from "./webflow"; import { PrintfulService } from "./printful"; +import { redis } from "bun"; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -18,14 +19,14 @@ type MainProduct = { }[]; }; -export class SyncService { - static state = { - isSyncing: false, - syncingIds: [] as number[], - }; +type SyncState = { + isSyncing: boolean; + syncingIds: number[]; +}; +export abstract class SyncService { static async sync(printfulProductId?: number) { - this.state.isSyncing = true; + await this.setState({ isSyncing: true, syncingIds: [] }); // Populate products const mainPrintfulProducts: MainProduct[] = []; @@ -79,8 +80,7 @@ export class SyncService { mainPrintfulProduct.colors.add(productColor); } } catch (error) { - this.state.isSyncing = false; - this.state.syncingIds = []; + await this.setState({ isSyncing: false, syncingIds: [] }); throw error; } @@ -90,10 +90,12 @@ export class SyncService { console.log("Syncing main products..."); for (const mainPrintfulProduct of mainPrintfulProducts) { try { - this.state.isSyncing = true; - this.state.syncingIds = mainPrintfulProduct.variants.map( - (v) => v.product.sync_product.id, - ); + await this.setState({ + isSyncing: true, + syncingIds: mainPrintfulProduct.variants.map( + (v) => v.product.sync_product.id, + ) + }); const foundColors = mainPrintfulProduct.colors; const foundSizes: Set = new Set(); @@ -313,12 +315,36 @@ export class SyncService { } } } - this.state.isSyncing = false; - this.state.syncingIds = []; + await this.setState({ isSyncing: false, syncingIds: [] }); console.log("Done"); } - private static getProductImageUrls = async (slug: string) => { + public static async setState(state: SyncState) { + await redis.set("commerce:sync:state", JSON.stringify(state)); + await redis.expire("commerce:sync:state", 1800); + } + + public static async getState(): Promise { + const val = await redis.get("commerce:sync:state"); + if (val) return JSON.parse(val); + return { isSyncing: false, syncingIds: [] } + } + + public static findColorInProductName(productName: string): string { + const m = productName.match(/\[([^\]]+)\]/); + return m?.[1] ? m[1] : "N/A"; + }; + + public static getMainProductName(productName: string) { + const colorInName = this.findColorInProductName(productName); + if (colorInName) { + return productName.replace(`[${colorInName}]`, "").trimEnd(); + } else { + return productName; + } + }; + + private static async getProductImageUrls(slug: string) { const res = await fetch(`${R2_WORKER_URL}/product-images/${slug}`); if (!res.ok) { console.error( @@ -330,18 +356,4 @@ export class SyncService { const productImageKeys: string[] = (await res.json()) as string[]; return productImageKeys.map((key) => `${WEBSITE_MEDIA_URL}/${key}`); }; - - public static findColorInProductName = (productName: string): string => { - const m = productName.match(/\[([^\]]+)\]/); - return m?.[1] ? m[1] : "N/A"; - }; - - public static getMainProductName = (productName: string) => { - const colorInName = this.findColorInProductName(productName); - if (colorInName) { - return productName.replace(`[${colorInName}]`, "").trimEnd(); - } else { - return productName; - } - }; }