From bb7c041c48d219a0b2d0edd7a7bfac1646b72e0c Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Sat, 3 May 2025 13:54:55 -0400 Subject: [PATCH] Setting up more printful logic. Setting up db. --- .gitignore | 3 ++ bun.lock | 19 ++++++++ database/data.db | Bin 0 -> 12288 bytes database/seed.sql | 5 ++ main.ts | 34 ++++++++++++- package.json | 9 ++++ printful.ts | 122 ++++++++++++++++++++++++++++++++++++++++++++++ storage.ts | 42 ++++++++++++++++ 8 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 bun.lock create mode 100644 database/data.db create mode 100644 database/seed.sql create mode 100644 package.json create mode 100644 printful.ts create mode 100644 storage.ts diff --git a/.gitignore b/.gitignore index eedd89b..282fabf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ api +node_modules/ +.env.* +./database/data.db diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..af2db4f --- /dev/null +++ b/bun.lock @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "devDependencies": { + "@types/bun": "^1.2.11", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.11", "", { "dependencies": { "bun-types": "1.2.11" } }, "sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw=="], + + "@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="], + + "bun-types": ["bun-types@1.2.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-dbkp5Lo8HDrXkLrONm6bk+yiiYQSntvFUzQp0v3pzTAsXk6FtgVMjdQ+lzFNVAmQFUkPQZ3WMZqH5tTo+Dp/IA=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + } +} diff --git a/database/data.db b/database/data.db new file mode 100644 index 0000000000000000000000000000000000000000..db8db7dc6a87553be5482cdfb1d25b183ec8f237 GIT binary patch literal 12288 zcmeI#K}*9h6bJC6ib`Q*x19>_xWQ0FJPOVcq)@7x?Zr#!Yzu|D&Msx11pRV;C66YR zTz2Wr!}vc)($^+w^Xut7ErL?zRMgFDs_2Rh8RzVlh%v@>JJQXD$&YQKN0avjXXhW! z1M$NgA(#;2yKe!WLjVF0fB*y_009U<00Izz00jO);PaZFjy#Wl-Kca`<(sC?+84F8 zqcdO1StKc%-35~DT^hTjW0$Xrs@^^Rt?@JR!${uCC4~eu}yJ?%IT&tq3`sT%< zzlh~{f7hvg?cN3xcXHvJjOHF^65fob(bghLz7E;W$OAf<2f+h&~yk0 zKmY;|fB*y_009U<00Izz00a(Iz%>U${eP&x7w3Wi1Rwwb2tWV=5P$##AOHafbOOJL CFjZ0j literal 0 HcmV?d00001 diff --git a/database/seed.sql b/database/seed.sql new file mode 100644 index 0000000..92e8498 --- /dev/null +++ b/database/seed.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS products ( + webflowProductId INTEGER NOT NULL, + printfulProductId INTEGER NOT NULL, + UNIQUE(webflowProductId, printfulProductId) +) diff --git a/main.ts b/main.ts index b179ee9..c0486f7 100644 --- a/main.ts +++ b/main.ts @@ -1 +1,33 @@ -console.log("Hello world"); +import { Printful } from "./printful.ts" +import { ProductRecords } from "./storage.ts" + +const productRecords = new ProductRecords(); + +const server = Bun.serve({ + routes: { + "/webhook/printful": { + async POST(req) { + const payload: Printful.Webhook.EventPayload = await req.json() + + switch (payload.type) { + case Printful.Webhook.Event.ProductUpdated: + const productRecord = productRecords.findFromPrintful(payload.data.sync_product.id) + if (productRecord) { + + } + else { + + } + break; + case Printful.Webhook.Event.ProductDeleted: + break; + } + + return Response.json("Hello world") + } + } + }, + development: true +}) + +console.log(`Listening on ${server.url}`); diff --git a/package.json b/package.json new file mode 100644 index 0000000..8f5b278 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": {}, + "devDependencies": { + "@types/bun": "^1.2.11" + }, + "scripts": { + "db:seed": "sqlite3 ./database/data.db < ./database/seed.sql" + } +} diff --git a/printful.ts b/printful.ts new file mode 100644 index 0000000..2127e60 --- /dev/null +++ b/printful.ts @@ -0,0 +1,122 @@ +export namespace Printful { + const API_ROUTE = "https://api.printful.com/store"; + + export namespace Webhook { + // meta + interface MetaData { + created: number; + retries: number; + store: number; + } + + export enum Event { + ProductUpdated = "product_updated", + ProductDeleted = "product_deleted" + } + + // product updated + export interface ProductUpdated extends MetaData { + type: Event.ProductUpdated, + data: { + sync_product: { + id: number; + external_id: string; + name: string; + variants: number; + synced: number; + thumbnail_url: string; + is_ignored: boolean; + }; + } + } + + // product deleted + export interface ProductDeleted extends MetaData { + type: Event.ProductDeleted, + data: { + sync_product: { + id: number; + external_id: string; + name: string; + }; + } + } + + export type EventPayload = ProductUpdated | ProductDeleted + } + + export namespace Products { + interface MetaData { + code: number; + result: T; + } + + interface Option { + id: string; + value: string; + } + + interface File { + type: string; + id: number; + url: string; + options: Option[]; + hash: string; + filename: string; + mime_type: string; + size: number; + width: number; + height: number; + dpi: number; + status: string; + created: number; + thumbnail_url: string; + preview_url: string; + visible: boolean; + is_temporary: boolean; + stitch_count_tier: string; + } + + interface SyncProductData { + id: number; + external_id: string; + sync_product_id: number; + name: string; + synced: boolean; + variant_id: number; + retail_price: string; + currency: string; + is_ignored: boolean; + sku: string; + product: { + variant_id: number; + product_id: number; + image: string; + name: string; + }; + files: File[]; + options: Option[]; + main_category_id: number; + warehouse_product_id: number; + warehouse_product_variant_id: number; + size: string; + color: string; + availability_status: string; + } + export type SyncProduct = MetaData; + } + + export async function getSyncProduct(syncProductId: String): Promise { + const payload: Products.SyncProduct = await ( + await fetch(`${API_ROUTE}/products/${syncProductId}`, { + method: "GET", + headers: { + "Authorization": `Bearer ${Bun.env.PRINTFUL_AUTH}`, + "X-PF-Store-Id": `${Bun.env.PRINTFUL_STORE_ID}` + } + }) + ).json(); + + return payload; + } +} diff --git a/storage.ts b/storage.ts new file mode 100644 index 0000000..9452b16 --- /dev/null +++ b/storage.ts @@ -0,0 +1,42 @@ +import { Database } from "bun:sqlite"; + +interface ProductsRecord { + webflowProductId: number; + printfulProductId: number; +} + +export class ProductRecords { + private db: Database; + + constructor() { + this.db = new Database("./database/data.db"); + } + + findFromWebflow(webflowProductId: number): ProductsRecord | undefined { + const row = this.db.query(` + SELECT * FROM product_records WHERE webflowProductId = ? + `).get(webflowProductId); + + return row as ProductsRecord | undefined; + } + + findFromPrintful(printfulProductId: number): ProductsRecord | undefined { + const row = this.db.query(` + SELECT * FROM product_records WHERE printfulProductId = ? + `).get(printfulProductId); + + return row as ProductsRecord | undefined; + } + + add(record: ProductsRecord): void { + this.db.run(` + INSERT OR IGNORE INTO product_records (webflowProductId, printfulProductId) + VALUES (?, ?) + `, [record.webflowProductId, record.printfulProductId]); + } + + all(): ProductsRecord[] { + return this.db.query(`SELECT * FROM product_records`).all() as ProductsRecord[]; + } +} +