Files
desa-darmasaba/prisma/migrate-seafile-to-minio.ts
nico fec6b79743 feat(storage): add Seafile→MinIO migration script and asset manifest
- migrate-seafile-to-minio.ts: downloads 80 assets from Seafile public
  share and re-uploads to MinIO with identical filenames (idempotent,
  skips existing objects)
- file-storage.json: asset manifest with names and Seafile download URLs
  used as migration source

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 12:06:40 +08:00

98 lines
2.7 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Script migrasi: download semua file dari Seafile public share → upload ke MinIO
* Jalankan sekali: bun run prisma/migrate-seafile-to-minio.ts
*/
import { Client } from "minio";
import fileStorageData from "./data/file-storage.json";
const SEAFILE_BASE_URL = "https://cld-dkr-makuro-seafile.wibudev.com";
const SEAFILE_SHARE_TOKEN = "3a9a9ecb5e244f4da8ae";
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 buildSeafileUrl(fileName: string): string {
return `${SEAFILE_BASE_URL}/d/${SEAFILE_SHARE_TOKEN}/files/?p=${encodeURIComponent(fileName)}&raw=1`;
}
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",
};
return map[ext ?? ""] ?? "application/octet-stream";
}
async function migrateFile(name: string): Promise<"ok" | "skip" | "error"> {
const objectName = `image/${name}`;
// Cek apakah sudah ada di MinIO — skip jika sudah
try {
await minio.statObject(BUCKET, objectName);
console.log(` ⏭ sudah ada, skip: ${name}`);
return "skip";
} catch {
// tidak ada, lanjut upload
}
const seafileUrl = buildSeafileUrl(name);
try {
const res = await fetch(seafileUrl);
if (!res.ok) {
console.error(` ✗ gagal download (HTTP ${res.status}): ${name}`);
return "error";
}
const buffer = Buffer.from(await res.arrayBuffer());
await minio.putObject(BUCKET, objectName, buffer, buffer.length, {
"Content-Type": guessMimeType(name),
});
console.log(`${name} (${(buffer.length / 1024).toFixed(1)} KB)`);
return "ok";
} catch (err) {
console.error(` ✗ error: ${name}`, err);
return "error";
}
}
async function main() {
console.log(`\n🚀 Migrasi Seafile → MinIO`);
console.log(` Bucket : ${BUCKET}`);
console.log(` Total : ${fileStorageData.length} file\n`);
let ok = 0, skip = 0, error = 0;
for (const item of fileStorageData) {
const result = await migrateFile(item.name);
if (result === "ok") ok++;
else if (result === "skip") skip++;
else error++;
}
console.log(`\n📊 Hasil:`);
console.log(` ✓ Berhasil : ${ok}`);
console.log(` ⏭ Dilewati : ${skip}`);
console.log(` ✗ Gagal : ${error}`);
if (error > 0) {
console.log(`\n⚠ Ada ${error} file yang gagal. Jalankan ulang script untuk retry.`);
process.exit(1);
} else {
console.log(`\n✅ Migrasi selesai!`);
}
}
main();