Files
starr-philproperties/services/chibisafe.service.js
T
2026-05-12 00:09:09 +08:00

213 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// services/chibisafe.service.js
//
// Wraps the Chibisafe REST API.
//
// Environment variables expected:
// CHIBISAFE_BASE_URL – e.g. https://cdn.yourdomain.com
// CHIBISAFE_API_KEY – your personal / service-account API key
// CHIBISAFE_ALBUM_AVATARS – album UUID for avatar images
// CHIBISAFE_ALBUM_VIDEOS – album UUID for videos
// CHIBISAFE_ALBUM_DOCUMENTS – album UUID for documents (pdf, docx, ppt, txt…)
// CHIBISAFE_ALBUM_THUMBNAILS – album UUID for video thumbnails
// CHIBISAFE_ALBUM_ARCHIVED – album UUID used as the "trash" / archived album
const FormData = require("form-data");
const fetch = require("node-fetch"); // npm i node-fetch@2 (CJS-compatible)
// ─── Config ───────────────────────────────────────────────────────────────────
const BASE_URL = (process.env.CHIBISAFE_BASE_URL || "").replace(/\/$/, "");
const API_KEY = process.env.CHIBISAFE_API_KEY || "";
const ALBUMS = {
avatars: process.env.CHIBISAFE_ALBUM_AVATARS || null,
videos: process.env.CHIBISAFE_ALBUM_VIDEOS || null,
documents: process.env.CHIBISAFE_ALBUM_DOCUMENTS || null,
thumbnails: process.env.CHIBISAFE_ALBUM_THUMBNAILS || null,
archived: process.env.CHIBISAFE_ALBUM_ARCHIVED || null,
};
// ─── Internal helpers ─────────────────────────────────────────────────────────
/**
* Resolve which Chibisafe album UUID should receive a file based on owner_type.
* owner_type is the single source of truth for album routing:
*
* "avatar" → avatars album (profile pictures)
* "video" → videos album (course/content videos)
* "document" → documents album (pdf, docx, ppt, txt…)
* "thumbnail" → thumbnails album (video cover images)
* "image" → no album (general-purpose images)
* anything else / null → no album
*
* @param {string} ownerType – value of the asset's owner_type field
* @returns {string|null}
*/
function resolveAlbumUuid(ownerType = "") {
switch (ownerType) {
case "avatar": return ALBUMS.avatars;
case "video": return ALBUMS.videos;
case "document": return ALBUMS.documents;
case "thumbnail": return ALBUMS.thumbnails;
default: return null; // "image" and unknowns → no album
}
}
/**
* Build default headers for every Chibisafe request.
*/
function baseHeaders(extra = {}) {
return {
"x-api-key": API_KEY,
...extra,
};
}
/**
* Thin fetch wrapper that throws a descriptive error on non-2xx.
*/
async function chibiRequest(path, options = {}) {
const url = `${BASE_URL}${path}`;
const res = await fetch(url, options);
let body;
try {
body = await res.json();
} catch {
body = {};
}
if (!res.ok) {
const msg = body?.message || body?.error || res.statusText;
const err = new Error(`[Chibisafe] ${res.status} – ${msg}`);
err.status = res.status;
err.chibiBody = body;
throw err;
}
return body;
}
// ─── Public API ───────────────────────────────────────────────────────────────
/**
* Upload a file to Chibisafe, optionally straight into a typed album.
*
* @param {object} opts
* @param {Buffer} opts.buffer – raw file bytes
* @param {string} opts.originalname – original filename (for Content-Disposition)
* @param {string} opts.mimetype – MIME type
* @param {string} opts.ownerType – asset owner_type value used to resolve the album
* ("avatar" | "video" | "document" | "thumbnail" | "image")
*
* @returns {Promise<{ uuid: string, url: string, name: string }>}
*/
async function uploadFile({ buffer, originalname, mimetype, ownerType = "" }) {
if (!BASE_URL || !API_KEY) {
throw new Error("[Chibisafe] CHIBISAFE_BASE_URL or CHIBISAFE_API_KEY is not configured.");
}
const albumUuid = resolveAlbumUuid(ownerType);
const form = new FormData();
form.append("file", buffer, {
filename: originalname,
contentType: mimetype,
});
const headers = {
...baseHeaders(),
...form.getHeaders(),
// Pass the album UUID at upload time so the file lands in the right album
// in a single round-trip (official Chibisafe header).
...(albumUuid ? { albumuuid: albumUuid } : {}),
};
const data = await chibiRequest("/api/upload", {
method: "POST",
headers,
body: form,
});
// Chibisafe returns: { name, uuid, url, ... }
return {
uuid: data.uuid,
url: data.url,
name: data.name,
};
}
/**
* Permanently delete one file from Chibisafe by its UUID.
* Used for rollback cleanup when a DB transaction fails after a successful upload.
*
* @param {string} uuid – Chibisafe file UUID
* @returns {Promise<void>}
*/
async function deleteFile(uuid) {
await chibiRequest(`/api/file/${uuid}`, {
method: "DELETE",
headers: baseHeaders(),
});
}
/**
* Move one or more files into the "archived" album (soft-delete equivalent).
* Preserves the file on Chibisafe but keeps it out of active albums.
*
* @param {string|string[]} uuids – Chibisafe file UUID(s)
* @returns {Promise<void>}
*/
async function archiveFiles(uuids) {
if (!ALBUMS.archived) {
throw new Error("[Chibisafe] CHIBISAFE_ALBUM_ARCHIVED is not configured.");
}
const ids = Array.isArray(uuids) ? uuids : [uuids];
if (!ids.length) return;
await chibiRequest("/api/files/album/add", {
method: "POST",
headers: {
...baseHeaders(),
"Content-Type": "application/json",
},
body: JSON.stringify({
files: ids,
albumUuid: ALBUMS.archived,
}),
});
}
/**
* Move one or more files into a specific album by UUID.
* Used internally; you can also call it directly for custom album operations.
*
* @param {string|string[]} uuids
* @param {string} albumUuid
* @returns {Promise<void>}
*/
async function addFilesToAlbum(uuids, albumUuid) {
const ids = Array.isArray(uuids) ? uuids : [uuids];
await chibiRequest("/api/files/album/add", {
method: "POST",
headers: {
...baseHeaders(),
"Content-Type": "application/json",
},
body: JSON.stringify({
files: ids,
albumUuid,
}),
});
}
module.exports = {
uploadFile,
deleteFile,
archiveFiles,
addFilesToAlbum,
ALBUMS,
resolveAlbumUuid,
};