cleanup
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
deploy-secrets:
|
deploy-secrets:
|
||||||
fly secrets import < .env
|
fly secrets import < .env
|
||||||
|
|
||||||
|
sync-products:
|
||||||
|
curl -X POST https://blade-and-brawn.fly.dev/products/sync
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export namespace Printful {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSyncProduct(syncProductId: number): Promise<SyncProduct> {
|
export async function get(syncProductId: number): Promise<SyncProduct> {
|
||||||
const payload: MetaDataSingle<SyncProduct> = await (
|
const payload: MetaDataSingle<SyncProduct> = await (
|
||||||
await fetch(`${Printful.API_URL}/store/products/${syncProductId}`, {
|
await fetch(`${Printful.API_URL}/store/products/${syncProductId}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -135,7 +135,7 @@ export namespace Printful {
|
|||||||
return payload.result;
|
return payload.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSyncProducts(): Promise<SyncProduct[]> {
|
export async function getAll(): Promise<SyncProduct[]> {
|
||||||
const payload: MetaDataMulti<SyncProduct["sync_product"]> = await (
|
const payload: MetaDataMulti<SyncProduct["sync_product"]> = await (
|
||||||
await fetch(`${Printful.API_URL}/store/products`, {
|
await fetch(`${Printful.API_URL}/store/products`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -148,9 +148,15 @@ export namespace Printful {
|
|||||||
|
|
||||||
// need to fetch variants
|
// need to fetch variants
|
||||||
const syncProducts = await Promise.all(
|
const syncProducts = await Promise.all(
|
||||||
payload.result.map(p => getSyncProduct(p.id))
|
payload.result.map(p => get(p.id))
|
||||||
);
|
);
|
||||||
return syncProducts;
|
return syncProducts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getVariantPreviewUrl(syncVariant: Printful.Products.SyncProduct["sync_variants"][number]): string {
|
||||||
|
const previewFile = syncVariant.files.find(f => f.type === "preview");
|
||||||
|
return previewFile?.preview_url ?? syncVariant.product.image;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import { Database } from "bun:sqlite";
|
|
||||||
|
|
||||||
interface ProductRecord {
|
|
||||||
webflowProductId: number;
|
|
||||||
printfulProductId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductRecords {
|
|
||||||
private db: Database;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const dbPath = Bun.env.NODE_ENV === "production" ?
|
|
||||||
"/data/data.db" :
|
|
||||||
"./data/data.db";
|
|
||||||
|
|
||||||
this.db = new Database(dbPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
findUsingWebflow(webflowProductId: number): ProductRecord | undefined {
|
|
||||||
const row = this.db.query(`
|
|
||||||
SELECT * FROM product_records WHERE webflowProductId = ?
|
|
||||||
`).get(webflowProductId);
|
|
||||||
|
|
||||||
return row as ProductRecord | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
findUsingPrintful(printfulProductId: number): ProductRecord | undefined {
|
|
||||||
const row = this.db.query(`
|
|
||||||
SELECT * FROM product_records WHERE printfulProductId = ?
|
|
||||||
`).get(printfulProductId);
|
|
||||||
|
|
||||||
return row as ProductRecord | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUsingWebflow(webflowProductId: number) {
|
|
||||||
this.db.run(`
|
|
||||||
DELETE FROM product_records WHERE webflowProductId = ?
|
|
||||||
`, [webflowProductId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUsingPrintful(printfulProductId: number) {
|
|
||||||
this.db.run(`
|
|
||||||
DELETE FROM product_records WHERE printfulProductId = ?
|
|
||||||
`, [printfulProductId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
add(record: ProductRecord): void {
|
|
||||||
this.db.run(`
|
|
||||||
INSERT OR IGNORE INTO product_records (webflowProductId, printfulProductId)
|
|
||||||
VALUES (?, ?)
|
|
||||||
`, [record.webflowProductId, record.printfulProductId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
all(): ProductRecord[] {
|
|
||||||
return this.db.query(`SELECT * FROM product_records`).all() as ProductRecord[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const productRecords = new ProductRecords();
|
|
||||||
+193
-190
@@ -1,4 +1,3 @@
|
|||||||
import { productRecords } from "./product-records";
|
|
||||||
import { Printful } from "./printful"
|
import { Printful } from "./printful"
|
||||||
|
|
||||||
export namespace Webflow {
|
export namespace Webflow {
|
||||||
@@ -15,7 +14,7 @@ export namespace Webflow {
|
|||||||
.replace(/^([^a-z0-9_])/, '_$1'); // if it starts with an invalid char, prefix underscore
|
.replace(/^([^a-z0-9_])/, '_$1'); // if it starts with an invalid char, prefix underscore
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Products {
|
export namespace Products {
|
||||||
export interface Sku {
|
export interface Sku {
|
||||||
fieldData: {
|
fieldData: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -30,12 +29,14 @@ export namespace Webflow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateProduct {
|
export interface UpsertProduct {
|
||||||
product: {
|
product: {
|
||||||
fieldData: {
|
fieldData: {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
shippable?: boolean;
|
||||||
|
"tax-category"?: string;
|
||||||
"sku-properties": {
|
"sku-properties": {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -48,13 +49,10 @@ export namespace Webflow {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
sku: Sku;
|
sku: Sku;
|
||||||
publishStatus: string;
|
publishStatus?: "staging" | "live";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteProductUsingPrintful(printfulProductId: number) {
|
export async function remove(webflowProductId: string) {
|
||||||
const webflowProductId = productRecords.findUsingPrintful(printfulProductId)?.webflowProductId;
|
|
||||||
if (webflowProductId) {
|
|
||||||
const res = await fetch(`${Webflow.API_COLLECTIONS_URL}/items/${webflowProductId}`, {
|
const res = await fetch(`${Webflow.API_COLLECTIONS_URL}/items/${webflowProductId}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -63,213 +61,218 @@ export namespace Webflow {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({})
|
body: JSON.stringify({})
|
||||||
});
|
});
|
||||||
productRecords.deleteUsingWebflow(webflowProductId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateProductUsingPrintful(printfulProduct: Printful.Products.SyncProduct) {
|
|
||||||
const webflowProductId = productRecords.findUsingPrintful(printfulProduct.sync_product.id)?.webflowProductId;
|
|
||||||
if (!webflowProductId) {
|
|
||||||
throw new Error("Product is not recognized");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const printfulVariants = printfulProduct.sync_variants;
|
export async function exists(webflowProductId: string): Promise<boolean> {
|
||||||
const webflowVariants: Products.Sku[] = [];
|
let findProductResponse = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}`, {
|
||||||
for (const printfulVariant of printfulVariants) {
|
method: "GET",
|
||||||
webflowVariants.push({
|
headers: {
|
||||||
"fieldData": {
|
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`,
|
||||||
"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: sync image
|
|
||||||
"main-image": "https://www.example.com/image.jpg"
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return findProductResponse.status === 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundColors = Array.from(new Set(printfulVariants.map(v => v.color)));
|
export async function updateUsingPrintful(printfulProduct: Printful.Products.SyncProduct) {
|
||||||
const foundSizes = Array.from(new Set(printfulVariants.map(v => v.size)));
|
const webflowProductId = printfulProduct.sync_product.external_id;
|
||||||
|
|
||||||
let updateProductResponse = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}`, {
|
const printfulVariants = printfulProduct.sync_variants;
|
||||||
method: "PATCH",
|
const webflowVariants: Products.Sku[] = [];
|
||||||
headers: {
|
for (const printfulVariant of printfulVariants) {
|
||||||
"Content-Type": "application/json",
|
webflowVariants.push({
|
||||||
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
"product": {
|
|
||||||
"fieldData": {
|
"fieldData": {
|
||||||
"name": printfulProduct.sync_product.name,
|
"name": printfulVariant.name,
|
||||||
"slug": formatSlug(printfulProduct.sync_product.name),
|
"slug": formatSlug(printfulVariant.name),
|
||||||
"sku-properties": [
|
"sku-values": {
|
||||||
{
|
"color": printfulVariant.color,
|
||||||
"id": "color",
|
"size": printfulVariant.size,
|
||||||
"name": "Color",
|
},
|
||||||
"enum": foundColors.map(color => ({
|
"price": {
|
||||||
"id": color,
|
"value": +printfulVariant.retail_price * 100,
|
||||||
"slug": formatSlug(color),
|
"unit": printfulVariant.currency,
|
||||||
"name": color
|
"currency": printfulVariant.currency
|
||||||
}))
|
},
|
||||||
},
|
"main-image": Printful.Products.getVariantPreviewUrl(printfulVariant)
|
||||||
{
|
|
||||||
"id": "size",
|
|
||||||
"name": "Size",
|
|
||||||
"enum": foundSizes.map(size => ({
|
|
||||||
"id": size,
|
|
||||||
"slug": formatSlug(size),
|
|
||||||
"name": size
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
"sku": webflowVariants[0]
|
|
||||||
})
|
|
||||||
});
|
|
||||||
updateProductResponse = await updateProductResponse.json();
|
|
||||||
|
|
||||||
const getProductResponse = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
const webflowProduct = await getProductResponse.json();
|
|
||||||
|
|
||||||
for (let i = 0; i < webflowProduct["skus"].length; ++i) {
|
const foundColors = Array.from(new Set(printfulVariants.map(v => v.color)));
|
||||||
const webflowSkuId = printfulVariants[i].external_id;
|
const foundSizes = Array.from(new Set(printfulVariants.map(v => v.size)));
|
||||||
|
|
||||||
const res = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}/skus/${webflowSkuId}`, {
|
let updateProductResponse = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"product": {
|
||||||
|
"fieldData": {
|
||||||
|
"name": printfulProduct.sync_product.name,
|
||||||
|
"slug": formatSlug(printfulProduct.sync_product.name),
|
||||||
|
"shippable": true,
|
||||||
|
"tax-category": "standard-taxable",
|
||||||
|
"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],
|
||||||
|
} as Products.UpsertProduct)
|
||||||
|
});
|
||||||
|
updateProductResponse = await updateProductResponse.json();
|
||||||
|
|
||||||
|
const getProductResponse = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const webflowProduct = await getProductResponse.json();
|
||||||
|
|
||||||
|
for (let i = 0; i < webflowProduct["skus"].length; ++i) {
|
||||||
|
const webflowSkuId = printfulVariants[i].external_id;
|
||||||
|
|
||||||
|
const res = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}/skus/${webflowSkuId}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"sku": webflowVariants[i]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUsingPrintful(printfulProduct: Printful.Products.SyncProduct) {
|
||||||
|
const printfulVariants = printfulProduct.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
|
||||||
|
},
|
||||||
|
"main-image": Printful.Products.getVariantPreviewUrl(printfulVariant)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (await fetch(`${Webflow.API_SITES_URL}/products`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"product": {
|
||||||
|
"fieldData": {
|
||||||
|
"name": printfulProduct.sync_product.name,
|
||||||
|
"slug": formatSlug(printfulProduct.sync_product.name),
|
||||||
|
"shippable": true,
|
||||||
|
"tax-category": "standard-taxable",
|
||||||
|
"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],
|
||||||
|
} as Products.UpsertProduct)
|
||||||
|
}))?.json();
|
||||||
|
|
||||||
|
if (!addProductResponse?.product?.id) {
|
||||||
|
console.error("Webflow product creation failed:", addProductResponse);
|
||||||
|
throw new Error("Failed to create Webflow product");
|
||||||
|
}
|
||||||
|
|
||||||
|
const webflowProductId = addProductResponse["product"]["id"];
|
||||||
|
const printfulProductId = printfulProduct.sync_product.id;
|
||||||
|
|
||||||
|
// CREATE WEBFLOW PRODUCT SKUs
|
||||||
|
let createSkuResponse = await fetch(`${Webflow.API_SITES_URL}/products/${webflowProductId}/skus`, {
|
||||||
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`
|
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"sku": webflowVariants[i]
|
"skus": webflowVariants.slice(1)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
createSkuResponse = await createSkuResponse.json();
|
||||||
}
|
|
||||||
|
|
||||||
export async function createProductUsingPrintful(printfulProduct: Printful.Products.SyncProduct) {
|
const responseSkus = [addProductResponse["sku"], ...createSkuResponse["skus"]];
|
||||||
const printfulVariants = printfulProduct.sync_variants;
|
|
||||||
|
|
||||||
const webflowVariants: Products.Sku[] = [];
|
// SYNC WEBFLOW/PRINTFUL PRODUCT AND VARIANT IDs
|
||||||
for (const printfulVariant of printfulVariants) {
|
let modifyPrintfulProductResponse = await fetch(`${Printful.API_URL}/store/products/${printfulProductId}`, {
|
||||||
webflowVariants.push({
|
method: "PUT",
|
||||||
"fieldData": {
|
headers: {
|
||||||
"name": printfulVariant.name,
|
"Content-Type": "application/json",
|
||||||
"slug": formatSlug(printfulVariant.name),
|
"Authorization": `Bearer ${Bun.env.PRINTFUL_AUTH}`,
|
||||||
"sku-values": {
|
"X-PF-Store-Id": Bun.env.PRINTFUL_STORE_ID ?? ""
|
||||||
"color": printfulVariant.color,
|
},
|
||||||
"size": printfulVariant.size,
|
body: JSON.stringify({
|
||||||
|
"sync_product": {
|
||||||
|
"external_id": String(webflowProductId)
|
||||||
},
|
},
|
||||||
"price": {
|
"sync_variants": responseSkus.map((sku, i) => ({
|
||||||
"value": +printfulVariant.retail_price * 100,
|
"id": Number(printfulVariants[i].id), // printful variant id
|
||||||
"unit": printfulVariant.currency,
|
"external_id": String(sku.id), // webflow variant id
|
||||||
"currency": printfulVariant.currency
|
}))
|
||||||
},
|
})
|
||||||
// TODO: sync image
|
|
||||||
// "main-image": "https://www.example.com/image.jpg"
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
modifyPrintfulProductResponse = await modifyPrintfulProductResponse.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (await fetch(`${Webflow.API_SITES_URL}/products`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": `bearer ${Bun.env.WEBFLOW_AUTH}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
"product": {
|
|
||||||
"fieldData": {
|
|
||||||
"name": printfulProduct.sync_product.name,
|
|
||||||
"slug": formatSlug(printfulProduct.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],
|
|
||||||
})
|
|
||||||
}))?.json();
|
|
||||||
|
|
||||||
if (!addProductResponse?.product?.id) {
|
|
||||||
console.error("Webflow product creation failed:", addProductResponse);
|
|
||||||
throw new Error("Failed to create Webflow product");
|
|
||||||
}
|
|
||||||
|
|
||||||
const webflowProductId = addProductResponse["product"]["id"];
|
|
||||||
const printfulProductId = printfulProduct.sync_product.id;
|
|
||||||
|
|
||||||
// CREATE WEBFLOW PRODUCT SKUs
|
|
||||||
let createSkuResponse = await fetch(`${Webflow.API_SITES_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 });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { calcAttributeLevels, calcPlayerLevel, computeLevels } from "./calculator/calc.ts";
|
import { calcAttributeLevels, calcPlayerLevel, computeLevels } from "./calculator/calc.ts";
|
||||||
import { Activity, BenchmarkPerformance, Player } from "./calculator/util.ts";
|
import { Activity, BenchmarkPerformance, Player } from "./calculator/util.ts";
|
||||||
import { productRecords } from "./commerce/product-records.ts";
|
|
||||||
import { Printful } from "./commerce/printful.ts"
|
import { Printful } from "./commerce/printful.ts"
|
||||||
import { Webflow } from "./commerce/webflow.ts";
|
import { Webflow } from "./commerce/webflow.ts";
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ export class ClientResponse extends Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
|
idleTimeout: 180,
|
||||||
routes: {
|
routes: {
|
||||||
"/calc": {
|
"/calc": {
|
||||||
OPTIONS() {
|
OPTIONS() {
|
||||||
@@ -66,27 +66,21 @@ const server = Bun.serve({
|
|||||||
return new ClientResponse(undefined, { status: 204 });
|
return new ClientResponse(undefined, { status: 204 });
|
||||||
},
|
},
|
||||||
async POST(req) {
|
async POST(req) {
|
||||||
const printfulProducts = await Printful.Products.getSyncProducts();
|
const printfulProducts = await Printful.Products.getAll();
|
||||||
|
|
||||||
// handle update/create
|
// handle update/create
|
||||||
for (const printfulProduct of printfulProducts) {
|
for (const printfulProduct of printfulProducts) {
|
||||||
const productRecord = productRecords.findUsingPrintful(printfulProduct.sync_product.id);
|
const webflowProductExists = await Webflow.Products.exists(printfulProduct.sync_product.external_id);
|
||||||
if (productRecord) {
|
if (webflowProductExists) {
|
||||||
await Webflow.updateProductUsingPrintful(printfulProduct);
|
await Webflow.Products.updateUsingPrintful(printfulProduct);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await Webflow.createProductUsingPrintful(printfulProduct);
|
await Webflow.Products.createUsingPrintful(printfulProduct);
|
||||||
}
|
}
|
||||||
|
console.log("DONE");
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle delete
|
// handle delete (TODO)
|
||||||
// for (const productRecord of productRecords.all()) {
|
|
||||||
// const existsInPrintful = printfulProducts
|
|
||||||
// .some(printfulProduct => printfulProduct.sync_product.id === productRecord.printfulProductId);
|
|
||||||
// if (!existsInPrintful) {
|
|
||||||
// await Webflow.deleteProductUsingPrintful(productRecord.printfulProductId);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return ClientResponse.json({});
|
return ClientResponse.json({});
|
||||||
}
|
}
|
||||||
@@ -101,20 +95,19 @@ const server = Bun.serve({
|
|||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
// ADD/UPDATE
|
// ADD/UPDATE
|
||||||
case Printful.Webhook.Event.ProductUpdated: {
|
case Printful.Webhook.Event.ProductUpdated: {
|
||||||
const printfulProduct = await Printful.Products.getSyncProduct(payload.data.sync_product.id);
|
const printfulProduct = await Printful.Products.get(payload.data.sync_product.id);
|
||||||
const productRecord = productRecords.findUsingPrintful(payload.data.sync_product.id);
|
const webflowProductExists = await Webflow.Products.exists(printfulProduct.sync_product.external_id);
|
||||||
|
if (webflowProductExists) {
|
||||||
if (productRecord) {
|
await Webflow.Products.updateUsingPrintful(printfulProduct);
|
||||||
await Webflow.updateProductUsingPrintful(printfulProduct);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await Webflow.createProductUsingPrintful(printfulProduct);
|
await Webflow.Products.createUsingPrintful(printfulProduct);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// DELETE
|
// DELETE
|
||||||
case Printful.Webhook.Event.ProductDeleted: {
|
case Printful.Webhook.Event.ProductDeleted: {
|
||||||
await Webflow.deleteProductUsingPrintful(payload.data.sync_product.id);
|
await Webflow.Products.remove(payload.data.sync_product.external_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user