From 6fc79f7541a40ca2b5cb8d81219a1d22b7edc84e Mon Sep 17 00:00:00 2001 From: nico Date: Thu, 23 Apr 2026 11:40:43 +0800 Subject: [PATCH] feat(storage): migrate file storage from local disk to MinIO Replace local filesystem-based image storage with MinIO S3-compatible object storage. All upload, serve, delete, and list operations now use the MinIO bucket defined in MINIO_* env vars. Co-Authored-By: Claude Sonnet 4.6 --- bun.lockb | Bin 185911 -> 200410 bytes package.json | 1 + src/app/api/[[...slugs]]/_lib/img-del.ts | 13 ++---- src/app/api/[[...slugs]]/_lib/img.ts | 41 +++++++----------- src/app/api/[[...slugs]]/_lib/imgs.ts | 30 ++++++------- .../api/[[...slugs]]/_lib/upl-img-single.ts | 13 +++--- src/app/api/[[...slugs]]/_lib/upl-img.ts | 37 +++++----------- src/app/api/[[...slugs]]/route.ts | 30 ++----------- src/lib/minio.ts | 12 +++++ 9 files changed, 70 insertions(+), 107 deletions(-) create mode 100644 src/lib/minio.ts diff --git a/bun.lockb b/bun.lockb index 624c5ecf1cbb2404ade1e52a883f53d9b7f711c4..2d40424d903909f4374584011922fac963a8c841 100755 GIT binary patch delta 41425 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 OUh}=dBB{Vu@_zu^m4od7 diff --git a/package.json b/package.json index d6976f97..2c8c3a0e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "framer-motion": "^12.4.1", "get-port": "^7.1.0", "lodash": "^4.17.21", + "minio": "^8.0.7", "motion": "^12.4.1", "nanoid": "^5.1.0", "next": "15.1.6", diff --git a/src/app/api/[[...slugs]]/_lib/img-del.ts b/src/app/api/[[...slugs]]/_lib/img-del.ts index 22b0c7f2..641e8653 100644 --- a/src/app/api/[[...slugs]]/_lib/img-del.ts +++ b/src/app/api/[[...slugs]]/_lib/img-del.ts @@ -1,15 +1,8 @@ -import fs from "fs/promises"; -import path from "path"; +import minio, { MINIO_BUCKET } from "@/lib/minio"; -async function imgDel({ - name, - UPLOAD_DIR_IMAGE, -}: { - name: string; - UPLOAD_DIR_IMAGE: string; -}) { +async function imgDel({ name }: { name: string }) { try { - await fs.unlink(path.join(UPLOAD_DIR_IMAGE, name)); + await minio.removeObject(MINIO_BUCKET, `image/${name}`); return "ok"; } catch (error) { console.log(error); diff --git a/src/app/api/[[...slugs]]/_lib/img.ts b/src/app/api/[[...slugs]]/_lib/img.ts index 4752afbf..e41bb4a7 100644 --- a/src/app/api/[[...slugs]]/_lib/img.ts +++ b/src/app/api/[[...slugs]]/_lib/img.ts @@ -1,27 +1,21 @@ -import fs from "fs/promises"; import path from "path"; +import fs from "fs/promises"; import sharp from "sharp"; +import minio, { MINIO_BUCKET } from "@/lib/minio"; async function img({ name, - UPLOAD_DIR_IMAGE, ROOT, size, }: { name: string; - UPLOAD_DIR_IMAGE: string; ROOT: string; - size?: number; // Ukuran opsional (tidak ada default) + size?: number; }) { - const completeName = path.basename(name); // Nama file lengkap - const ext = path.extname(name).toLowerCase(); // Ekstensi file dalam huruf kecil - // const fileNameWithoutExt = path.basename(name, ext); // Nama file tanpa ekstensi - - // Default image jika terjadi kesalahan const noImage = path.join(ROOT, "public/no-image.jpg"); + const ext = path.extname(name).toLowerCase(); - // Validasi ekstensi file - if (![".jpg", ".jpeg", ".png"].includes(ext)) { + if (![".jpg", ".jpeg", ".png", ".webp"].includes(ext)) { console.warn(`Ekstensi file tidak didukung: ${ext}`); return new Response(await fs.readFile(noImage), { headers: { "Content-Type": "image/jpeg" }, @@ -29,29 +23,26 @@ async function img({ } try { - // Path ke file asli - const filePath = path.join(UPLOAD_DIR_IMAGE, completeName); + const stream = await minio.getObject(MINIO_BUCKET, `image/${name}`); + const chunks: Buffer[] = []; + for await (const chunk of stream) { + chunks.push(Buffer.from(chunk)); + } + const buffer = Buffer.concat(chunks); - // Periksa apakah file ada - await fs.stat(filePath); - - // Metadata gambar asli - const metadata = await sharp(filePath).metadata(); - - // Proses resize menggunakan sharp - const resizedImageBuffer = await sharp(filePath) - .resize(size || metadata.width) // Gunakan size jika diberikan, jika tidak gunakan width asli + const metadata = await sharp(buffer).metadata(); + const resized = await sharp(buffer) + .resize(size || metadata.width) .toBuffer(); - return new Response(resizedImageBuffer, { + return new Response(resized, { headers: { "Cache-Control": "public, max-age=3600, stale-while-revalidate=600", "Content-Type": "image/jpeg", }, }); } catch (error) { - console.error(`Gagal memproses file: ${name}`, error); - // Jika file tidak ditemukan atau gagal diproses, kembalikan default image + console.error(`Gagal mengambil file dari MinIO: ${name}`, error); return new Response(await fs.readFile(noImage), { headers: { "Content-Type": "image/jpeg" }, }); diff --git a/src/app/api/[[...slugs]]/_lib/imgs.ts b/src/app/api/[[...slugs]]/_lib/imgs.ts index 5b3470f7..fdd439a9 100644 --- a/src/app/api/[[...slugs]]/_lib/imgs.ts +++ b/src/app/api/[[...slugs]]/_lib/imgs.ts @@ -1,30 +1,30 @@ -import fs from "fs/promises"; +import minio, { MINIO_BUCKET } from "@/lib/minio"; async function imgs({ search = "", page = 1, count = 20, - UPLOAD_DIR_IMAGE, }: { search?: string; page?: number; count?: number; - UPLOAD_DIR_IMAGE: string; }) { - const files = await fs.readdir(UPLOAD_DIR_IMAGE); + const objects: { name: string; url: string }[] = []; - return files - .filter( - (file) => - file.endsWith(".jpg") || file.endsWith(".png") || file.endsWith(".jpeg") - ) - .filter((file) => file.includes(search)) + const stream = minio.listObjects(MINIO_BUCKET, "image/", true); + + for await (const obj of stream) { + if (!obj.name) continue; + const fileName = obj.name.replace("image/", ""); + if (!fileName) continue; + if (search && !fileName.includes(search)) continue; + objects.push({ name: fileName, url: `/api/img/${fileName}` }); + } + + const total = objects.length; + return objects .slice((page - 1) * count, page * count) - .map((file) => ({ - name: file, - url: `/api/img/${file}`, - total: files.length, - })); + .map((o) => ({ ...o, total })); } export default imgs; diff --git a/src/app/api/[[...slugs]]/_lib/upl-img-single.ts b/src/app/api/[[...slugs]]/_lib/upl-img-single.ts index a8d7c7a2..a4ef65da 100644 --- a/src/app/api/[[...slugs]]/_lib/upl-img-single.ts +++ b/src/app/api/[[...slugs]]/_lib/upl-img-single.ts @@ -1,30 +1,31 @@ import { nanoid } from "nanoid"; -import fs from "fs/promises"; import path from "path"; import _ from "lodash"; +import minio, { MINIO_BUCKET } from "@/lib/minio"; export async function uplImgSingle({ fileName, file, - UPLOAD_DIR_IMAGE, }: { fileName: string; file: File; - UPLOAD_DIR_IMAGE: string; }) { if (!fileName || typeof fileName !== "string" || fileName.trim() === "") { console.warn(`Nama file tidak valid: ${fileName}`); fileName = nanoid() + ".jpg"; } + const ext = path.extname(fileName).toLowerCase(); const fileNameWithoutExt = path.basename(fileName, ext); const fileNameKebabCase = _.kebabCase(fileNameWithoutExt) + ext; + const objectName = `image/${fileNameKebabCase}`; try { const buffer = Buffer.from(await file.arrayBuffer()); - const filePath = path.join(UPLOAD_DIR_IMAGE, fileNameKebabCase); - await fs.writeFile(filePath, buffer); - return filePath; + await minio.putObject(MINIO_BUCKET, objectName, buffer, buffer.length, { + "Content-Type": file.type || "image/jpeg", + }); + return objectName; } catch (error) { console.log(error); return "error"; diff --git a/src/app/api/[[...slugs]]/_lib/upl-img.ts b/src/app/api/[[...slugs]]/_lib/upl-img.ts index 9fdf482e..de519f4f 100644 --- a/src/app/api/[[...slugs]]/_lib/upl-img.ts +++ b/src/app/api/[[...slugs]]/_lib/upl-img.ts @@ -1,15 +1,11 @@ -import path from "path"; -import fs from "fs/promises"; import { nanoid } from "nanoid"; +import minio, { MINIO_BUCKET } from "@/lib/minio"; -async function uplImg({ - files, - UPLOAD_DIR_IMAGE, -}: { - files: File[]; - UPLOAD_DIR_IMAGE: string; -}) { - // Validasi input +function sanitizeFileName(fileName: string): string { + return fileName.replace(/[^a-zA-Z0-9._\-]/g, "_"); +} + +async function uplImg({ files }: { files: File[] }) { if (!Array.isArray(files) || files.length === 0) { throw new Error("Tidak ada file yang diunggah"); } @@ -17,24 +13,20 @@ async function uplImg({ for (const file of files) { let fileName = file.name; - // Validasi nama file if (!fileName || typeof fileName !== "string" || fileName.trim() === "") { console.warn(`Nama file tidak valid: ${fileName}`); fileName = nanoid() + ".jpg"; } - // Sanitasi nama file untuk mencegah path traversal - const sanitizedFileName = sanitizeFileName(fileName); + const sanitized = sanitizeFileName(fileName); + const objectName = `image/${sanitized}`; try { - // Konversi file ke buffer const buffer = Buffer.from(await file.arrayBuffer()); - - // Tulis file ke direktori uploads - const filePath = path.join(UPLOAD_DIR_IMAGE, sanitizedFileName); - await fs.writeFile(filePath, buffer); - - console.log(`File berhasil diunggah: ${sanitizedFileName}`); + await minio.putObject(MINIO_BUCKET, objectName, buffer, buffer.length, { + "Content-Type": file.type || "image/jpeg", + }); + console.log(`File berhasil diunggah ke MinIO: ${objectName}`); } catch (error) { console.error(`Gagal mengunggah file ${fileName}:`, error); throw new Error(`Gagal mengunggah file: ${fileName}`); @@ -44,9 +36,4 @@ async function uplImg({ return "ok"; } -// Fungsi untuk membersihkan nama file dari karakter yang tidak aman -function sanitizeFileName(fileName: string): string { - return fileName.replace(/[^a-zA-Z0-9._\-]/g, "_"); -} - export default uplImg; diff --git a/src/app/api/[[...slugs]]/route.ts b/src/app/api/[[...slugs]]/route.ts index a84cb826..e8ffd1a2 100644 --- a/src/app/api/[[...slugs]]/route.ts +++ b/src/app/api/[[...slugs]]/route.ts @@ -4,32 +4,15 @@ import swagger from "@elysiajs/swagger"; import { Elysia, t } from "elysia"; import getPotensi from "./_lib/get-potensi"; import img from "./_lib/img"; -import fs from "fs/promises"; -import path from "path"; import uplImg from "./_lib/upl-img"; import imgs from "./_lib/imgs"; import uplCsv from "./_lib/upl-csv"; import imgDel from "./_lib/img-del"; import { uplImgSingle } from "./_lib/upl-img-single"; import { uplCsvSingle } from "./_lib/upl-csv-single"; + const ROOT = process.cwd(); -if (!process.env.WIBU_UPLOAD_DIR) - throw new Error("WIBU_UPLOAD_DIR is not defined"); - -const UPLOAD_DIR = path.join(ROOT, process.env.WIBU_UPLOAD_DIR); -const UPLOAD_DIR_IMAGE = path.join(UPLOAD_DIR, "image"); - -// create uploads dir -fs.mkdir(UPLOAD_DIR, { - recursive: true, -}).catch(() => {}); - -// create image uploads dir -fs.mkdir(UPLOAD_DIR_IMAGE, { - recursive: true, -}).catch(() => {}); - const corsConfig = { origin: "*", methods: ["GET", "POST", "PATCH", "DELETE", "PUT"] as HTTPMethod[], @@ -43,6 +26,7 @@ async function layanan() { const data = await prisma.layanan.findMany(); return { data }; } + const ApiServer = new Elysia() .use(swagger({ path: "/api/docs" })) .use(cors(corsConfig)) @@ -63,7 +47,6 @@ const ApiServer = new Elysia() ({ params, query }) => { return img({ name: params.name, - UPLOAD_DIR_IMAGE, ROOT, size: query.size, }); @@ -82,10 +65,7 @@ const ApiServer = new Elysia() .delete( "/img/:name", ({ params }) => { - return imgDel({ - name: params.name, - UPLOAD_DIR_IMAGE, - }); + return imgDel({ name: params.name }); }, { params: t.Object({ @@ -100,7 +80,6 @@ const ApiServer = new Elysia() search: query.search, page: query.page, count: query.count, - UPLOAD_DIR_IMAGE, }); }, { @@ -117,7 +96,7 @@ const ApiServer = new Elysia() "/upl-img", ({ body }) => { console.log(body.title); - return uplImg({ files: body.files, UPLOAD_DIR_IMAGE }); + return uplImg({ files: body.files }); }, { body: t.Object({ @@ -132,7 +111,6 @@ const ApiServer = new Elysia() return uplImgSingle({ fileName: body.name, file: body.file, - UPLOAD_DIR_IMAGE, }); }, { diff --git a/src/lib/minio.ts b/src/lib/minio.ts new file mode 100644 index 00000000..32712102 --- /dev/null +++ b/src/lib/minio.ts @@ -0,0 +1,12 @@ +import { Client } from "minio"; + +const minioClient = new Client({ + endPoint: process.env.MINIO_ENDPOINT!, + accessKey: process.env.MINIO_ACCESS_KEY!, + secretKey: process.env.MINIO_SECRET_KEY!, + useSSL: process.env.MINIO_USE_SSL === "true", +}); + +export const MINIO_BUCKET = process.env.MINIO_BUCKET!; + +export default minioClient;