hooking up commerce
This commit is contained in:
+21
-7
@@ -29,14 +29,28 @@ export const app = new Elysia({ prefix: "/api" })
|
|||||||
|
|
||||||
// COMMERCE
|
// COMMERCE
|
||||||
|
|
||||||
.post("/products/sync", async () => {
|
.post("/products/sync", async ({ body, set }) => {
|
||||||
const printfulProducts = await Printful.Products.getAll();
|
console.log("Syncing...");
|
||||||
for (const p of printfulProducts) {
|
try {
|
||||||
const exists = await Webflow.Products.exists(p.sync_product.external_id);
|
const printfulProducts = await Printful.Products.getAll();
|
||||||
if (exists) await Webflow.Products.updateUsingPrintful(p);
|
const webflowProducts = await Webflow.Products.getAll();
|
||||||
else await Webflow.Products.createUsingPrintful(p);
|
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
|
// WEBHOOKS
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ export namespace Printful {
|
|||||||
export interface MetaDataMulti<T = any> {
|
export interface MetaDataMulti<T = any> {
|
||||||
code: number;
|
code: number;
|
||||||
result: T[];
|
result: T[];
|
||||||
|
paging: {
|
||||||
|
total: number,
|
||||||
|
offset: number,
|
||||||
|
limit: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
@@ -117,7 +122,6 @@ export namespace Printful {
|
|||||||
size: string;
|
size: string;
|
||||||
color: string;
|
color: string;
|
||||||
availability_status: string;
|
availability_status: string;
|
||||||
|
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,22 +139,32 @@ export namespace Printful {
|
|||||||
return payload.result;
|
return payload.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAll(): Promise<SyncProduct[]> {
|
export async function getAll(offset: number = 0): Promise<SyncProduct["sync_product"][]> {
|
||||||
const payload: MetaDataMulti<SyncProduct["sync_product"]> = await (
|
const allSyncProducts: SyncProduct["sync_product"][] = [];
|
||||||
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();
|
|
||||||
|
|
||||||
// need to fetch variants
|
while (true) {
|
||||||
const syncProducts = await Promise.all(
|
try {
|
||||||
payload.result.map(p => get(p.id))
|
const payload: MetaDataMulti<SyncProduct["sync_product"]> = await (
|
||||||
);
|
await fetch(`${Printful.API_URL}/store/products?offset=${offset}`, {
|
||||||
return syncProducts;
|
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_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}`;
|
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 => {
|
const formatSlug = (name: string): string => {
|
||||||
return name
|
return name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -15,7 +17,18 @@ export namespace Webflow {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export namespace Products {
|
export namespace Products {
|
||||||
|
export interface Product {
|
||||||
|
id: string,
|
||||||
|
fieldData: {
|
||||||
|
name: string,
|
||||||
|
slug: string,
|
||||||
|
description: string
|
||||||
|
},
|
||||||
|
skus: Sku[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface Sku {
|
export interface Sku {
|
||||||
|
id?: string,
|
||||||
fieldData: {
|
fieldData: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -52,8 +65,19 @@ export namespace Webflow {
|
|||||||
publishStatus?: "staging" | "live";
|
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) {
|
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",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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