From d5da472661c81ad56a98126fc2830ef561035624 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Sat, 3 May 2025 16:33:11 -0400 Subject: [PATCH] Create product and SKUs implementation --- .gitignore | 2 +- database/data.db | Bin 12288 -> 12288 bytes storage.ts => database/product-records.ts | 16 +-- database/seed.sql | 2 +- main.ts | 8 +- printful.ts | 64 +++++---- webflow.ts | 153 ++++++++++++++++++++++ 7 files changed, 206 insertions(+), 39 deletions(-) rename storage.ts => database/product-records.ts (68%) create mode 100644 webflow.ts diff --git a/.gitignore b/.gitignore index 282fabf..5390e37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ api node_modules/ .env.* -./database/data.db +database/data.db diff --git a/database/data.db b/database/data.db index db8db7dc6a87553be5482cdfb1d25b183ec8f237..bfc0d2dbe8ce642d5b39579f41dae11d24d1c5b2 100644 GIT binary patch delta 137 zcmZojXh@il$; } - export async function getSyncProduct(syncProductId: String): Promise { + export async function getSyncProduct(syncProductId: number): Promise { const payload: Products.SyncProduct = await ( - await fetch(`${API_ROUTE}/products/${syncProductId}`, { + await fetch(`${API_URL}/products/${syncProductId}`, { method: "GET", headers: { "Authorization": `Bearer ${Bun.env.PRINTFUL_AUTH}`, diff --git a/webflow.ts b/webflow.ts new file mode 100644 index 0000000..4e2d0d2 --- /dev/null +++ b/webflow.ts @@ -0,0 +1,153 @@ +import { ProductRecords } from "./database/product-records"; +import { Printful } from "./printful" + +export namespace Webflow { + export const API_URL = `https://api.webflow.com/v2/sites/${Bun.env.WEBFLOW_SITE_ID}`; + + const formatSlug = name => name.replaceAll(" ", "-").replaceAll("/", "").toLowerCase(); + + namespace Products { + export interface Sku { + fieldData: { + name: string; + slug: string; + "sku-values"?: Record, + price: { + value: number; + unit: string; + currency: string; + }; + "main-image": string; + } + } + + export interface CreateProduct { + product: { + fieldData: { + name: string; + slug: string; + description: string; + "sku-properties": { + id: string; + name: string; + enum: { + id: string; + name: string; + slug: string; + }[]; + }[]; + } + }; + sku: Sku; + publishStatus: string; + } + } + + export async function createProduct(printfulProduct: Printful.Products.SyncProduct, productRecords: ProductRecords) { + const printfulVariants = printfulProduct.result.sync_variants; + + const webflowVariants: Products.Sku[] = []; + for (const printfulVariant of printfulVariants) { + webflowVariants.push({ + "fieldData": { + "name": printfulVariant.name, + "slug": formatSlug(printfulVariant.name), + "sku-values": { + "color": printfulVariant.color, + "size": printfulVariant.size, + }, + "price": { + "value": +printfulVariant.retail_price * 100, + "unit": printfulVariant.currency, + "currency": printfulVariant.currency + }, + // TODO + "main-image": "https://www.example.com/image.jpg" + } + }); + } + + const foundColors = Array.from(new Set(printfulVariants.map(v => v.color))); + const foundSizes = Array.from(new Set(printfulVariants.map(v => v.size))); + + // CREATE WEBFLOW PRODUCT + let addProductResponse = await fetch(`${API_URL}/products`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`, + }, + body: JSON.stringify({ + "product": { + "fieldData": { + "name": printfulProduct.result.sync_product.name, + "slug": formatSlug(printfulProduct.result.sync_product.name), + "sku-properties": [ + { + "id": "color", + "name": "Color", + "enum": foundColors.map(color => ({ + "id": color, + "slug": formatSlug(color), + "name": color + })) + }, + { + "id": "size", + "name": "Size", + "enum": foundSizes.map(size => ({ + "id": size, + "slug": formatSlug(size), + "name": size + })) + }, + ] + } + }, + "sku": webflowVariants[0], + }) + }); + addProductResponse = await addProductResponse.json(); + + const webflowProductId = addProductResponse["product"]["id"]; + const printfulProductId = printfulProduct.result.sync_product.id; + + // CREATE WEBFLOW PRODUCT SKUs + let createSkuResponse = await fetch(`${API_URL}/products/${webflowProductId}/skus`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}` + }, + body: JSON.stringify({ + "skus": webflowVariants.slice(1) + }) + }); + createSkuResponse = await createSkuResponse.json(); + + const responseSkus = [addProductResponse["sku"], ...createSkuResponse["skus"]]; + + // SYNC WEBFLOW/PRINTFUL PRODUCT AND VARIANT IDs + let modifyPrintfulProductResponse = await fetch(`${Printful.API_URL}/store/products/${printfulProductId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${Bun.env.PRINTFUL_AUTH}`, + "X-PF-Store-Id": Bun.env.PRINTFUL_STORE_ID ?? "" + }, + body: JSON.stringify({ + "sync_product": { + "external_id": String(webflowProductId) + }, + "sync_variants": responseSkus.map((sku, i) => ({ + "id": Number(printfulVariants[i].id), // printful variant id + "external_id": String(sku["id"]), // webflow variant id + })) + }) + }); + modifyPrintfulProductResponse = await modifyPrintfulProductResponse.json(); + + // CACHE WEBFLOW/PRINTFUL PRODUCT ASSOCIATION + productRecords.add({ printfulProductId, webflowProductId }); + } +}