import { Client } from "minio"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); } const MINIO_BUCKET = process.env.MINIO_BUCKET; const minio = new Client({ endPoint: process.env.MINIO_HOST ?? "localhost", port: process.env.MINIO_PORT ? +process.env.MINIO_PORT : undefined, useSSL: /true/.test(process.env.MINIO_SSL || "false"), accessKey: process.env.MINIO_ACCESS_KEY ?? "", secretKey: process.env.MINIO_SECRET_KEY ?? "", }); export default minio; /** * ตัดชื่อไฟล์ถ้ายาวเกิน limit * @param fileName - ชื่อไฟล์เดิม * @param maxLength - ความยาวสูงสุดของชื่อไฟล์ (default: 50 ตัวอักษร ไม่รวมนามสกุล) * @returns ชื่อไฟล์ที่ตัดแล้ว */ export function truncateFileName(fileName: string, maxLength: number = 50): string { const dot = fileName.lastIndexOf("."); const name = dot !== -1 && !fileName.startsWith(".") ? fileName.slice(0, dot) : fileName; const ext = dot !== -1 && !fileName.startsWith(".") ? fileName.slice(dot) : ""; if (name.length <= maxLength) { return fileName; } // ตัดชื่อและเติม "..." ตามด้วย 5 ตัวสุดท้ายของชื่อเดิม const truncated = name.slice(0, maxLength - 8) + "..." + name.slice(-5); return truncated + ext; } function exception(e: any): never { console.error(e); throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์"); } export async function listFile(path: string) { return await new Promise((resolve, reject) => { const item: string[] = []; const stream = minio.listObjectsV2(MINIO_BUCKET, path); stream.on("data", (v) => v && v.name && item.push(v.name.split("/").at(-1) || "")); stream.on("end", () => resolve(item)); stream.on("error", (e) => { console.error(e); reject(new Error("MinIO error.")); }); }); } export async function getPresigned(method: string, path: string, exp = 60 * 60) { return await minio.presignedUrl(method, MINIO_BUCKET, path, exp); } export async function getFile(path: string, exp = 60 * 60) { return await minio.presignedGetObject(MINIO_BUCKET, path, exp); } export async function setFile(path: string, exp = 6 * 60 * 60) { return await minio.presignedPutObject(MINIO_BUCKET, path, exp); } export async function s3DeleteFile(path: string) { await minio.removeObject(MINIO_BUCKET, path, { forceDelete: true }); } export async function s3DeleteFolder(path: string) { new Promise((resolve, reject) => { const item: string[] = []; const stream = minio.listObjectsV2(MINIO_BUCKET, path, true); stream.on("data", (v) => v && v.name && item.push(v.name)); stream.on("end", () => resolve(item)); stream.on("error", (e) => { console.error(e); reject(new Error("MinIO error.")); }); }).then((list) => { list.map(async (v) => { await minio.removeObject(MINIO_BUCKET, v, { forceDelete: true }); }); }); } export async function s3UploadFile(replace: boolean, ...pathname: string[]) { if (!pathname.length) return; // ตัดชื่อไฟล์ถ้ายาวเกิน const originalFileName = pathname.at(-1) as string; const truncatedFileName = truncateFileName(originalFileName); pathname = [...pathname.slice(0, -1), truncatedFileName]; if (!replace) { const list = await s3ListFile(...pathname.slice(0, -1)).catch(exception); const fileName = pathname.at(-1) as string; const dot = fileName.lastIndexOf("."); const name = dot !== -1 && !fileName.startsWith(".") ? fileName.slice(0, dot) : fileName; const ext = dot !== -1 && !fileName.startsWith(".") ? fileName.slice(dot) : ""; let copy = 0; let copyFileName = fileName; while (list.findIndex((v) => v.fileName === copyFileName) !== -1) { copyFileName = `${name} (${++copy})${ext}`.trim(); } if (copy > 0) { await s3UpdateFile(pathname, pathname.slice(0, -1).concat(copyFileName)); } } const data = { path: pathname.slice(0, -1).join("/"), pathname: pathname.join("/"), fileName: pathname.at(-1), title: pathname.at(-1), uploadUrl: await setFile(fileLocation(...pathname)).catch(exception), }; return data; } export async function s3UpdateFile(from: string[], to: string[], upload?: boolean) { await minio .copyObject(MINIO_BUCKET, to.join("/"), [MINIO_BUCKET].concat(from).join("/")) .catch(exception); await minio.removeObject(MINIO_BUCKET, from.join("/")).catch(exception); const data = { path: to.slice(0, -1).join("/"), pathname: to.join("/"), fileName: to.at(-1), title: to.at(-1), uploadUrl: upload ? await setFile(fileLocation(...to)).catch(exception) : undefined, }; return data; } export async function s3ListFile(...path: string[]) { const list = await listFile(fileLocation(...path) + "/").catch(exception); return list.map((name) => ({ path: path.join("/"), pathname: path.concat(name).join("/"), fileName: name, title: name, })); } export async function s3DownloadFile(...pathname: string[]) { const downloadUrl = await getFile(fileLocation(...pathname)).catch(exception); return { path: pathname.slice(0, -1).join("/"), pathname: pathname.join("/"), fileName: pathname.at(-1), title: pathname.at(-1), downloadUrl, }; } export async function duplicateAvatarFile(refId: string, prefix: string, fileName: string) { try { await minio.statObject(MINIO_BUCKET, refId); const avatar = `${prefix}/${fileName}`; await minio.copyObject( MINIO_BUCKET, avatar, `/${MINIO_BUCKET}/${refId}` ); } catch (error) { exception(error); } } export const fileLocation = (...path: string[]) => path.join("/");