187 lines
6.9 KiB
TypeScript
187 lines
6.9 KiB
TypeScript
import type { Printful } from "./util/types";
|
|
import { type DeepPartial, type PagingOptions } from "./util/misc";
|
|
|
|
const parsePayload = (res: Response): Promise<unknown> =>
|
|
res.json().catch(() => res.text().catch(() => undefined));
|
|
|
|
type ClientOptions = {
|
|
token: string;
|
|
storeId: string;
|
|
};
|
|
|
|
type Credentials = {
|
|
apiUrl: string;
|
|
authHeaders: Record<string, string>;
|
|
};
|
|
|
|
export class PrintfulError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public upstreamStatus: number,
|
|
public payload: unknown,
|
|
public status: number = 502
|
|
) {
|
|
super(message);
|
|
this.name = "PrintfulError";
|
|
}
|
|
}
|
|
|
|
class VariantsClient {
|
|
constructor(private creds: Credentials) { }
|
|
|
|
async get(variantId: number | string): Promise<Printful.Products.SyncVariant | undefined> {
|
|
const res = await fetch(`${this.creds.apiUrl}/store/variants/${variantId}`, {
|
|
method: "GET",
|
|
headers: this.creds.authHeaders,
|
|
});
|
|
if (res.status === 404 || res.status === 400) return;
|
|
if (!res.ok) throw new PrintfulError("Failed to get Printful variant", res.status, await parsePayload(res));
|
|
const payload = (await res.json()) as Printful.MetaDataSingle<Printful.Products.SyncVariant>;
|
|
return payload.result;
|
|
}
|
|
}
|
|
|
|
class ProductsClient {
|
|
readonly Variants: VariantsClient;
|
|
|
|
constructor(private creds: Credentials) {
|
|
this.Variants = new VariantsClient(creds);
|
|
}
|
|
|
|
async list(opt: PagingOptions = {}): Promise<Printful.Products.SyncProduct[]> {
|
|
const allSyncProducts: Printful.Products.SyncProduct[] = [];
|
|
let offset = opt.offset ?? 0;
|
|
do {
|
|
const res = await fetch(`${this.creds.apiUrl}/store/products?offset=${offset}&limit=${opt.limit ?? 100}`, {
|
|
method: "GET",
|
|
headers: this.creds.authHeaders,
|
|
});
|
|
if (!res.ok) throw new PrintfulError("Failed to list Printful products", res.status, await parsePayload(res));
|
|
const payload = (await res.json()) as Printful.MetaDataMulti<Printful.Products.SyncProduct>;
|
|
allSyncProducts.push(...payload.result);
|
|
offset = allSyncProducts.length;
|
|
if (allSyncProducts.length >= payload.paging.total) break;
|
|
} while (opt.forceAll);
|
|
return allSyncProducts;
|
|
}
|
|
|
|
async get(productId: number | string): Promise<Printful.Products.Product | undefined> {
|
|
const res = await fetch(`${this.creds.apiUrl}/store/products/${productId}`, {
|
|
method: "GET",
|
|
headers: this.creds.authHeaders,
|
|
});
|
|
if (res.status === 404 || res.status === 400) return;
|
|
if (!res.ok) throw new PrintfulError("Failed to get Printful product", res.status, await parsePayload(res));
|
|
const payload = (await res.json()) as Printful.MetaDataSingle<Printful.Products.Product>;
|
|
return payload.result;
|
|
}
|
|
|
|
async update(pProductId: number, pProduct: DeepPartial<Printful.Products.Product>) {
|
|
const res = await fetch(`${this.creds.apiUrl}/store/products/${pProductId}`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...this.creds.authHeaders,
|
|
},
|
|
body: JSON.stringify(pProduct),
|
|
});
|
|
if (!res.ok) throw new PrintfulError("Failed to update Printful product", res.status, await parsePayload(res));
|
|
}
|
|
}
|
|
|
|
class OrdersClient {
|
|
constructor(private creds: Credentials) { }
|
|
|
|
async create(pOrder: Printful.Orders.Order) {
|
|
const res = await fetch(`${this.creds.apiUrl}/orders`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...this.creds.authHeaders,
|
|
},
|
|
body: JSON.stringify(pOrder),
|
|
});
|
|
if (!res.ok) throw new PrintfulError("Failed to create Printful order", res.status, await parsePayload(res));
|
|
}
|
|
|
|
async list(opt: PagingOptions = {}): Promise<Printful.Orders.Order[]> {
|
|
const allPOrders: Printful.Orders.Order[] = [];
|
|
let offset = opt.offset ?? 0;
|
|
do {
|
|
const res = await fetch(`${this.creds.apiUrl}/orders?offset=${offset}&limit=${opt.limit ?? 100}`, {
|
|
method: "GET",
|
|
headers: this.creds.authHeaders,
|
|
});
|
|
if (!res.ok) throw new PrintfulError("Failed to list Printful orders", res.status, await parsePayload(res));
|
|
const payload = (await res.json()) as Printful.MetaDataMulti<Printful.Orders.Order>;
|
|
allPOrders.push(...payload.result);
|
|
offset = allPOrders.length;
|
|
if (allPOrders.length >= payload.paging.total) break;
|
|
} while (opt.forceAll);
|
|
return allPOrders;
|
|
}
|
|
}
|
|
|
|
class WebhooksClient {
|
|
constructor(private creds: Credentials) { }
|
|
|
|
async get(): Promise<Printful.Webhook.Config | undefined> {
|
|
const res = await fetch(`${this.creds.apiUrl}/webhooks`, {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...this.creds.authHeaders,
|
|
}
|
|
});
|
|
if (res.status === 404 || res.status === 400) return;
|
|
if (!res.ok) throw new PrintfulError("Failed to get webhook configuration", res.status, await parsePayload(res));
|
|
const payload = (await res.json()) as Printful.Webhook.Config;
|
|
return payload;
|
|
}
|
|
|
|
|
|
/**
|
|
* Printful only supports one URL at a time, so subsequent calls will overwrite the previous webhook
|
|
*/
|
|
async create(url: string, events: Printful.Webhook.Event[]) {
|
|
const res = await fetch(`${this.creds.apiUrl}/webhooks`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...this.creds.authHeaders,
|
|
},
|
|
body: JSON.stringify({
|
|
"url": url,
|
|
"types": events
|
|
}),
|
|
});
|
|
if (!res.ok) throw new PrintfulError("Failed to register webhook URL", res.status, await parsePayload(res));
|
|
}
|
|
}
|
|
|
|
export class PrintfulClient {
|
|
readonly Products: ProductsClient;
|
|
readonly Orders: OrdersClient;
|
|
readonly Webhooks: WebhooksClient;
|
|
|
|
constructor(options: ClientOptions) {
|
|
const creds: Credentials = {
|
|
apiUrl: "https://api.printful.com",
|
|
authHeaders: {
|
|
Authorization: `Bearer ${options.token}`,
|
|
"X-PF-Store-Id": options.storeId,
|
|
},
|
|
};
|
|
this.Products = new ProductsClient(creds);
|
|
this.Orders = new OrdersClient(creds);
|
|
this.Webhooks = new WebhooksClient(creds);
|
|
}
|
|
|
|
static Util = {
|
|
getVariantMainImage(syncVariant: Printful.Products.SyncVariant): string {
|
|
const previewFile = syncVariant.files.find((f) => f.type === "preview");
|
|
return previewFile?.preview_url ?? syncVariant.product.image;
|
|
},
|
|
};
|
|
}
|