From 9ba60fa1f3ae23fb7fda9cb44fd3b8e6ad9924bb Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Sat, 20 Sep 2025 21:02:00 -0400 Subject: [PATCH] hooking up commerce --- src/lib/api.ts | 28 +++++++++--- src/lib/services/commerce/printful.ts | 46 ++++++++++++------- src/lib/services/commerce/webflow.ts | 26 ++++++++++- src/routes/commerce/+page.server.ts | 31 +++++++++++++ src/routes/commerce/+page.svelte | 64 +++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 24 deletions(-) create mode 100644 src/routes/commerce/+page.server.ts create mode 100644 src/routes/commerce/+page.svelte diff --git a/src/lib/api.ts b/src/lib/api.ts index 0de2536..d82bde3 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -29,14 +29,28 @@ export const app = new Elysia({ prefix: "/api" }) // COMMERCE - .post("/products/sync", async () => { - const printfulProducts = await Printful.Products.getAll(); - for (const p of printfulProducts) { - const exists = await Webflow.Products.exists(p.sync_product.external_id); - if (exists) await Webflow.Products.updateUsingPrintful(p); - else await Webflow.Products.createUsingPrintful(p); + .post("/products/sync", async ({ body, set }) => { + console.log("Syncing..."); + try { + const printfulProducts = await Printful.Products.getAll(); + const webflowProducts = await Webflow.Products.getAll(); + for (const p of printfulProducts) { + const exists = webflowProducts.some(wp => wp.id === p.external_id); + + const fullPrintfulProduct = await Printful.Products.get(p.id); + if (exists) + await Webflow.Products.updateUsingPrintful(fullPrintfulProduct); + else + await Webflow.Products.createUsingPrintful(fullPrintfulProduct); + } } - return {}; + catch (error) { + console.error(error); + set.status = 500; + return { error: "internal_error" }; + } + console.log("Done"); + return ""; }) // WEBHOOKS diff --git a/src/lib/services/commerce/printful.ts b/src/lib/services/commerce/printful.ts index 9e5d1ff..eb2bc0c 100644 --- a/src/lib/services/commerce/printful.ts +++ b/src/lib/services/commerce/printful.ts @@ -54,6 +54,11 @@ export namespace Printful { export interface MetaDataMulti { code: number; result: T[]; + paging: { + total: number, + offset: number, + limit: number + } } interface Option { @@ -117,7 +122,6 @@ export namespace Printful { size: string; color: string; availability_status: string; - }[] } @@ -135,22 +139,32 @@ export namespace Printful { return payload.result; } - export async function getAll(): Promise { - const payload: MetaDataMulti = await ( - await fetch(`${Printful.API_URL}/store/products`, { - method: "GET", - headers: { - "Authorization": `Bearer ${Bun.env.PRINTFUL_AUTH}`, - "X-PF-Store-Id": `${Bun.env.PRINTFUL_STORE_ID}` - } - }) - ).json(); + export async function getAll(offset: number = 0): Promise { + const allSyncProducts: SyncProduct["sync_product"][] = []; - // need to fetch variants - const syncProducts = await Promise.all( - payload.result.map(p => get(p.id)) - ); - return syncProducts; + while (true) { + try { + const payload: MetaDataMulti = await ( + await fetch(`${Printful.API_URL}/store/products?offset=${offset}`, { + method: "GET", + headers: { + "Authorization": `Bearer ${Bun.env.PRINTFUL_AUTH}`, + "X-PF-Store-Id": `${Bun.env.PRINTFUL_STORE_ID}` + } + }) + ).json(); + + allSyncProducts.push(...payload.result); + offset = allSyncProducts.length; + + if (allSyncProducts.length >= payload.paging.total) + break; + } catch (error) { + break; + } + } + + return allSyncProducts; } diff --git a/src/lib/services/commerce/webflow.ts b/src/lib/services/commerce/webflow.ts index 84aac34..6e0f548 100644 --- a/src/lib/services/commerce/webflow.ts +++ b/src/lib/services/commerce/webflow.ts @@ -4,6 +4,8 @@ export namespace Webflow { export const API_SITES_URL = `https://api.webflow.com/v2/sites/${Bun.env.WEBFLOW_SITE_ID}`; export const API_COLLECTIONS_URL = `https://api.webflow.com/v2/collections/${Bun.env.WEBFLOW_COLLECTION_ID}`; + const authHeader = { "Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}` }; + const formatSlug = (name: string): string => { return name .toLowerCase() @@ -15,7 +17,18 @@ export namespace Webflow { }; export namespace Products { + export interface Product { + id: string, + fieldData: { + name: string, + slug: string, + description: string + }, + skus: Sku[] + } + export interface Sku { + id?: string, fieldData: { name: string; slug: string; @@ -52,8 +65,19 @@ export namespace Webflow { publishStatus?: "staging" | "live"; } + export async function getAll(): Promise { + let res = await fetch(`${Webflow.API_SITES_URL}/products`, { + method: "GET", + headers: { + ...authHeader, + } + }); + const resObj = await res.json(); + return resObj.items as Product[]; + } + export async function remove(webflowProductId: string) { - const res = await fetch(`${Webflow.API_COLLECTIONS_URL}/items/${webflowProductId}`, { + await fetch(`${Webflow.API_COLLECTIONS_URL}/items/${webflowProductId}`, { method: "DELETE", headers: { "Content-Type": "application/json", diff --git a/src/routes/commerce/+page.server.ts b/src/routes/commerce/+page.server.ts new file mode 100644 index 0000000..f00a0ae --- /dev/null +++ b/src/routes/commerce/+page.server.ts @@ -0,0 +1,31 @@ +import { Printful } from '$lib/services/commerce/printful'; +import { Webflow } from '$lib/services/commerce/webflow'; +import type { PageServerLoad } from './$types'; + +const productsStore = { + cache: { + printful: undefined as Printful.Products.SyncProduct["sync_product"][] | undefined, + webflow: undefined as Webflow.Products.Product[] | undefined, + }, + getPrintfulProducts: async () => { + if (!productsStore.cache.printful) { + productsStore.cache.printful = await Printful.Products.getAll() + } + return productsStore.cache.printful; + }, + getWebflowProducts: async () => { + if (!productsStore.cache.webflow) { + productsStore.cache.webflow = await Webflow.Products.getAll() + } + return productsStore.cache.webflow; + } +}; + +export const load = async ({ }: Parameters[0]) => { + return { + products: { + printful: productsStore.getPrintfulProducts(), + webflow: productsStore.getWebflowProducts(), + } + }; +}; diff --git a/src/routes/commerce/+page.svelte b/src/routes/commerce/+page.svelte new file mode 100644 index 0000000..ff8eff8 --- /dev/null +++ b/src/routes/commerce/+page.svelte @@ -0,0 +1,64 @@ + + +{#await data.products.printful} +

Loading...

+{:then printfulProducts} + {#if isSyncing} +

Syncing...

+ {:else} + +
+ + + + + + + + + {#each printfulProducts as printfulProduct} + + + + + {/each} + +
NameSynced
{printfulProduct.name} + {#await isPrintfulProductSynced(printfulProduct) then isSynced} +
+ {isSynced ? "Synced" : "Desynced"} +
+ {/await} +
+
+ {/if} +{:catch error} +

Something went wrong: {error.message}

+{/await}