fix(storage): migrate fileStorage handlers and seeder from local disk/Seafile to MinIO

Root cause: images not showing because:
1. seed_assets.ts was listing from Seafile (unreliable, 502 errors)
2. fileStorage create/findUniq/del handlers used local disk (not available in containers)
3. link format in file-storage.json had inconsistent path prefix

Changes:
- seed_assets.ts: list objects from MinIO bucket instead of Seafile API; seed
  link as /api/img/{name}, path as "image" (matches MinIO prefix)
- fileStorage/create.ts: upload image/audio/document to MinIO via putObject;
  remove WIBU_UPLOAD_DIR dependency and all fs.writeFile calls
- fileStorage/findUniq.ts: stream file from MinIO via getObject; remove
  WIBU_UPLOAD_DIR and fs.readFile
- fileStorage/del.ts: delete from MinIO via removeObject; remove fs.unlink
- file-storage.json: fix path field to "image" (was full path); all 80 entries
  already have correct link=/api/img/{name} format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-23 14:28:08 +08:00
parent d145611221
commit 2958950585
5 changed files with 433 additions and 299 deletions

View File

@@ -1,49 +1,92 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Client } from "minio";
import { getAllPublicCdnUrls } from "./lib/create_file_share_folder";
const minio = 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",
});
const BUCKET = process.env.MINIO_BUCKET!;
function guessMimeType(fileName: string): string {
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
const map: Record<string, string> = {
webp: "image/webp",
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
gif: "image/gif",
mp3: "audio/mpeg",
mp4: "audio/mp4",
pdf: "application/pdf",
};
return map[ext] ?? "application/octet-stream";
}
export default async function seedAssets() {
const images = await getAllPublicCdnUrls();
console.log("📂 Seeding assets dari MinIO...");
const stream = minio.listObjects(BUCKET, "image/", true);
let seeded = 0;
let updated = 0;
let skipped = 0;
for await (const obj of stream) {
if (!obj.name) continue;
const fileName = obj.name.replace("image/", "");
if (!fileName) continue;
const mimeType = guessMimeType(fileName);
for (const img of images) {
try {
// Check if the image already exists by name
const existingImage = await prisma.fileStorage.findUnique({
where: { name: img.name },
const existing = await prisma.fileStorage.findUnique({
where: { name: fileName },
});
if (!existingImage) {
// Only create if it doesn't exist
await prisma.fileStorage.create({
data: {
name: img.name,
category: "image",
mimeType: "image/webp",
link: img.cdnUrl,
path: "images",
realName: img.name,
isActive: true,
},
});
console.log(`✅ Created new image: ${img.name}`);
} else {
console.log(` Image already exists, skipping: ${img.name}`);
if (existing) {
// Perbaiki link lama yang masih pakai Seafile URL atau format lama
if (!existing.link?.startsWith("/api/img/")) {
await prisma.fileStorage.update({
where: { name: fileName },
data: { link: `/api/img/${fileName}`, path: "image" },
});
console.log(` 🔄 Updated: ${fileName}`);
updated++;
} else {
skipped++;
}
continue;
}
await prisma.fileStorage.create({
data: {
name: fileName,
realName: fileName,
path: "image",
mimeType,
category: "image",
link: `/api/img/${fileName}`,
isActive: true,
},
});
console.log(` ✓ Seeded: ${fileName}`);
seeded++;
} catch (err) {
console.log(`❌ Failed to seed asset ${img.name}:`, JSON.stringify(err));
console.error(` ✗ Error: ${fileName}`, err);
}
}
console.log("🎉 Image seeding completed");
console.log(`\n🎉 Asset seeding selesai — seeded: ${seeded}, updated: ${updated}, skipped: ${skipped}`);
}
if (import.meta.main) {
seedAssets()
.then(() => {
console.log("seed assets success");
})
.then(() => prisma.$disconnect())
.catch((err) => {
console.log("gagal seed assets", JSON.stringify(err));
console.error("gagal seed assets", err);
prisma.$disconnect();
process.exit(1);
});
}