Supa-backup / run_db_backup (push) Successful in 38s
- Added carbone-sdk@^1.6.0 to package.json and deno.lock - Created a new notebook (carbone.ipynb) with CarboneClient class for interacting with the Carbone API - Implemented methods for listing templates, categories, tags, uploading templates, and rendering documents
17 KiB
17 KiB
import { load } from "jsr:@std/dotenv";
const env = await load({
export: true,
});
type ListTemplatesParams = {
id?: string;
versionId?: string;
category?: string;
origin?: 0 | 1;
includeVersions?: boolean;
search?: string;
limit?: number;
cursor?: string;
};
class CarboneClient {
listTemplates(params?: ListTemplatesParams) {}
listCategories() {}
listTags() {}
uploadTemplate(input: {
file?: Buffer;
filename?: string;
template?: string; // base64
versioning?: boolean;
id?: string;
name?: string;
comment?: string;
tags?: string[];
category?: string;
sample?: any[];
deployedAt?: number;
expireAt?: number;
}) {}
patchTemplate(id: string, body: {
name?: string;
comment?: string;
tags?: string[];
category?: string;
deployedAt?: number;
expireAt?: number;
}) {}
deleteTemplate(id: string) {}
downloadTemplate(id: string) {}
render(templateId: string, body: any, opts?: { download?: boolean }) {}
renderFromTemplate(body: any, opts?: { download?: boolean }) {}
downloadRender(renderId: string) {}
}
const CARBONE_VERSION = "5";
type CarboneSuccess<T> = {
success: true;
data: T;
hasMore?: boolean;
nextCursor?: string;
};
type CarboneError = {
success: false;
error: string;
};
type CarboneResponse<T> = CarboneSuccess<T> | CarboneError;
export class CarboneClient {
constructor(
private baseUrl: string,
private apiToken: string
) {}
private buildHeaders(extra?: Record<string, string>) {
return {
Authorization: `Bearer ${this.apiToken}`,
"carbone-version": CARBONE_VERSION,
...extra,
};
}
private async parseResponse<T>(res: Response): Promise<T> {
const contentType = res.headers.get("content-type") || "";
if (!res.ok) {
let message = `Carbone request failed: ${res.status}`;
try {
const err = await res.json();
message = err?.error || err?.message || message;
} catch {}
throw new Error(message);
}
if (contentType.includes("application/json")) {
return res.json() as Promise<T>;
}
return res as unknown as T;
}
private toQuery(params?: Record<string, unknown>) {
const qs = new URLSearchParams();
if (!params) return "";
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null) continue;
qs.set(key, String(value));
}
const s = qs.toString();
return s ? `?${s}` : "";
}
async listTemplates(params?: {
id?: string;
versionId?: string;
category?: string;
origin?: 0 | 1;
includeVersions?: boolean;
search?: string;
limit?: number;
cursor?: string;
}) {
const res = await fetch(
`${this.baseUrl}/templates${this.toQuery(params)}`,
{
method: "GET",
headers: this.buildHeaders(),
}
);
return this.parseResponse<
CarboneResponse<
Array<{
id: string;
versionId: string;
deployedAt: number;
createdAt: number;
expireAt?: number;
size: number;
type: string;
name?: string;
category?: string;
comment?: string;
tags?: string[];
origin?: number;
}>
>
>(res);
}
async listCategories() {
const res = await fetch(`${this.baseUrl}/templates/categories`, {
method: "GET",
headers: this.buildHeaders(),
});
return this.parseResponse<CarboneResponse<Array<{ name: string }>>>(res);
}
async listTags() {
const res = await fetch(`${this.baseUrl}/templates/tags`, {
method: "GET",
headers: this.buildHeaders(),
});
return this.parseResponse<CarboneResponse<Array<{ name: string }>>>(res);
}
async uploadTemplateJson(body: {
template: string;
versioning?: boolean;
id?: string;
name?: string;
comment?: string;
tags?: string[];
category?: string;
sample?: any[];
deployedAt?: number;
expireAt?: number;
}) {
const res = await fetch(`${this.baseUrl}/template`, {
method: "POST",
headers: this.buildHeaders({
"Content-Type": "application/json",
}),
body: JSON.stringify(body),
});
return this.parseResponse<
CarboneResponse<{
id?: string;
versionId?: string;
templateId?: string;
type?: string;
size?: number;
createdAt?: number;
}>
>(res);
}
async uploadTemplateFile(input: {
file: Blob;
filename: string;
versioning?: boolean;
id?: string;
name?: string;
comment?: string;
tags?: string[];
category?: string;
sample?: any[];
deployedAt?: number;
expireAt?: number;
}) {
const form = new FormData();
if (input.versioning !== undefined) {
form.append("versioning", String(input.versioning));
}
if (input.id) form.append("id", input.id);
if (input.name) form.append("name", input.name);
if (input.comment) form.append("comment", input.comment);
if (input.category) form.append("category", input.category);
if (input.tags) {
for (const tag of input.tags) form.append("tags[]", tag);
}
if (input.sample) form.append("sample", JSON.stringify(input.sample));
if (input.deployedAt !== undefined) {
form.append("deployedAt", String(input.deployedAt));
}
if (input.expireAt !== undefined) {
form.append("expireAt", String(input.expireAt));
}
// template should be appended last
form.append("template", input.file, input.filename);
const res = await fetch(`${this.baseUrl}/template`, {
method: "POST",
headers: this.buildHeaders(),
body: form,
});
return this.parseResponse<
CarboneResponse<{
id?: string;
versionId?: string;
templateId?: string;
type?: string;
size?: number;
createdAt?: number;
}>
>(res);
}
async patchTemplate(
id: string,
body: {
name?: string;
comment?: string;
tags?: string[];
category?: string;
deployedAt?: number;
expireAt?: number;
}
) {
const res = await fetch(`${this.baseUrl}/template/${id}`, {
method: "PATCH",
headers: this.buildHeaders({
"Content-Type": "application/json",
}),
body: JSON.stringify(body),
});
return this.parseResponse<CarboneResponse<true>>(res);
}
async deleteTemplate(id: string) {
const res = await fetch(`${this.baseUrl}/template/${id}`, {
method: "DELETE",
headers: this.buildHeaders(),
});
return this.parseResponse<CarboneResponse<true>>(res);
}
async downloadTemplate(id: string) {
const res = await fetch(`${this.baseUrl}/template/${id}`, {
method: "GET",
headers: this.buildHeaders(),
});
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.error || `Failed to download template: ${res.status}`);
}
return {
buffer: Buffer.from(await res.arrayBuffer()),
contentType: res.headers.get("content-type"),
contentDisposition: res.headers.get("content-disposition"),
};
}
async render(
templateIdOrVersionId: string,
body: Record<string, unknown>,
opts?: { download?: boolean }
) {
const query = opts?.download ? "?download=true" : "";
const res = await fetch(
`${this.baseUrl}/render/${templateIdOrVersionId}${query}`,
{
method: "POST",
headers: this.buildHeaders({
"Content-Type": "application/json",
}),
body: JSON.stringify(body),
}
);
if (opts?.download) {
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.error || `Render failed: ${res.status}`);
}
return {
buffer: Buffer.from(await res.arrayBuffer()),
contentType: res.headers.get("content-type"),
contentDisposition: res.headers.get("content-disposition"),
};
}
return this.parseResponse<CarboneResponse<{ renderId: string }>>(res);
}
async renderFromTemplate(
body: Record<string, unknown>,
opts?: { download?: boolean }
) {
const query = opts?.download ? "?download=true" : "";
const res = await fetch(`${this.baseUrl}/render/template${query}`, {
method: "POST",
headers: this.buildHeaders({
"Content-Type": "application/json",
}),
body: JSON.stringify(body),
});
if (opts?.download) {
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.error || `Render failed: ${res.status}`);
}
return {
buffer: Buffer.from(await res.arrayBuffer()),
contentType: res.headers.get("content-type"),
contentDisposition: res.headers.get("content-disposition"),
};
}
return this.parseResponse<CarboneResponse<{ renderId: string }>>(res);
}
async downloadRender(renderId: string) {
const res = await fetch(`${this.baseUrl}/render/${renderId}`, {
method: "GET",
});
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.error || `Failed to download render: ${res.status}`);
}
return {
buffer: Buffer.from(await res.arrayBuffer()),
contentType: res.headers.get("content-type"),
contentDisposition: res.headers.get("content-disposition"),
};
}
}
const carbone = new CarboneClient(
"https://carbone.lci.ulsa.mx",
Deno.env.get("CARBONE_API_TOKEN")!
);
const templates = await carbone.listTemplates({ limit: 20 });
console.log(templates);
const categories = await carbone.listCategories();
console.log(categories);
const tags = await carbone.listTags();
console.log(tags);
const templateID = "1373945625988937236";
await carbone.patchTemplate("1373945625988937236", {
category: "Planes de estudio",
comment: "Plantilla de anexo 1 SEP RVOE Acuerdo 17-11-17",
});