Setting up more printful logic. Setting up db.
This commit is contained in:
@@ -1 +1,4 @@
|
|||||||
api
|
api
|
||||||
|
node_modules/
|
||||||
|
.env.*
|
||||||
|
./database/data.db
|
||||||
|
|||||||
@@ -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=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS products (
|
||||||
|
webflowProductId INTEGER NOT NULL,
|
||||||
|
printfulProductId INTEGER NOT NULL,
|
||||||
|
UNIQUE(webflowProductId, printfulProductId)
|
||||||
|
)
|
||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "^1.2.11"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"db:seed": "sqlite3 ./database/data.db < ./database/seed.sql"
|
||||||
|
}
|
||||||
|
}
|
||||||
+122
@@ -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<T = any> {
|
||||||
|
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<SyncProductData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSyncProduct(syncProductId: String): Promise<Products.SyncProduct> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -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[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user