hooking up commerce
This commit is contained in:
+21
-7
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user