hooking up commerce

This commit is contained in:
Dominic Ferrando
2025-09-20 21:02:00 -04:00
parent 0555793bf3
commit 9ba60fa1f3
5 changed files with 171 additions and 24 deletions
+21 -7
View File
@@ -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
+30 -16
View File
@@ -54,6 +54,11 @@ export namespace Printful {
export interface MetaDataMulti<T = any> {
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<SyncProduct[]> {
const payload: MetaDataMulti<SyncProduct["sync_product"]> = 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<SyncProduct["sync_product"][]> {
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<SyncProduct["sync_product"]> = 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;
}
+25 -1
View File
@@ -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<Product[]> {
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",
+31
View File
@@ -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<PageServerLoad>[0]) => {
return {
products: {
printful: productsStore.getPrintfulProducts(),
webflow: productsStore.getWebflowProducts(),
}
};
};
+64
View File
@@ -0,0 +1,64 @@
<script lang="ts">
import { Printful } from "$lib/services/commerce/printful.js";
const { data } = $props();
let isSyncing = $state(false);
const isPrintfulProductSynced = async (
printfulProduct: Printful.Products.SyncProduct["sync_product"],
) => {
const webflowProducts = await data.products.webflow;
return webflowProducts.some(
(p) => p.id === printfulProduct.external_id,
);
};
const syncProducts = async () => {
isSyncing = true;
await fetch(`http://localhost:5173/api/products/sync`, {
method: "POST",
});
isSyncing = false;
};
</script>
{#await data.products.printful}
<p>Loading...</p>
{:then printfulProducts}
{#if isSyncing}
<p>Syncing...</p>
{:else}
<button onclick={() => syncProducts()} class="btn btn-lg">sync</button>
<div class="w-full overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Synced</th>
</tr>
</thead>
<tbody>
{#each printfulProducts as printfulProduct}
<tr class="hover:bg-base-300">
<td>{printfulProduct.name}</td>
<td>
{#await isPrintfulProductSynced(printfulProduct) then isSynced}
<div
class="badge {isSynced
? 'badge-success'
: 'badge-error'}"
>
{isSynced ? "Synced" : "Desynced"}
</div>
{/await}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
{:catch error}
<p>Something went wrong: {error.message}</p>
{/await}