chore: relocate backend into apps/api ahead of monorepo merge

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 12:20:40 +08:00
co-authored by Claude Sonnet 5
parent af44598df6
commit eaa6d2276a
388 changed files with 0 additions and 13998 deletions
+178
View File
@@ -0,0 +1,178 @@
// 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 axios = require("axios");
// ─── 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,
images: process.env.CHIBISAFE_ALBUM_IMAGES || null, // ← new
archived: process.env.CHIBISAFE_ALBUM_ARCHIVED || null,
};
// ─── Internal helpers ─────────────────────────────────────────────────────────
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;
case "image": return ALBUMS.images;
default: return null;
}
}
function baseHeaders(extra = {}) {
return {
"x-api-key": API_KEY,
...extra,
};
}
// ─── Axios instance ───────────────────────────────────────────────────────────
const chibi = axios.create({
baseURL: BASE_URL,
maxBodyLength: Infinity, // ← required for large file uploads
maxContentLength: Infinity,
});
/**
* Thin axios wrapper that throws a descriptive error on non-2xx.
*/
async function chibiRequest(path, { method = "GET", headers = {}, data } = {}) {
try {
const res = await chibi.request({
url: path,
method,
headers,
data,
});
return res.data;
} catch (err) {
const status = err.response?.status;
const body = err.response?.data ?? {};
const msg = body?.message || body?.error || err.message;
const friendly = new Error(`[Chibisafe] ${status ?? "?"} – ${msg}`);
friendly.status = status;
friendly.chibiBody = body;
throw friendly;
}
}
// ─── Public API ───────────────────────────────────────────────────────────────
/**
* Upload a file to Chibisafe, optionally straight into a typed album.
*/
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 contentLength = await new Promise((resolve, reject) => {
form.getLength((err, length) => (err ? reject(err) : resolve(length)));
});
const data = await chibiRequest("/api/upload", {
method: "POST",
headers: {
...baseHeaders(),
...form.getHeaders(),
"Content-Length": contentLength,
...(albumUuid ? { albumuuid: albumUuid } : {}),
},
data: form,
});
return { uuid: data.uuid, url: data.url, name: data.name };
}
/**
* Permanently delete one file from Chibisafe by its UUID.
*/
async function deleteFile(uuid) {
await chibiRequest(`/api/file/${uuid}`, {
method: "DELETE",
headers: baseHeaders(),
});
}
/**
* Move one or more files into the "archived" album.
*/
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",
},
data: {
files: ids,
albumUuid: ALBUMS.archived,
},
});
}
/**
* Move one or more files into a specific album by UUID.
*/
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",
},
data: {
files: ids,
albumUuid,
},
});
}
module.exports = {
uploadFile,
deleteFile,
archiveFiles,
addFilesToAlbum,
ALBUMS,
resolveAlbumUuid,
};