// /* eslint-disable @typescript-eslint/no-unused-vars */ // // prisma/seedAssets.ts // import prisma from "@/lib/prisma"; // import AdmZip from "adm-zip"; // import fs from "fs/promises"; // import path from "path"; // import sharp from "sharp"; // import fetchWithRetry from "./data/fetchWithRetry"; // const UPLOADS_DIR = path.resolve( // process.env.WIBU_UPLOAD_DIR || "uploads" // ); // // --- Helper: deteksi kategori file --- // function detectCategory(filename: string): "image" | "document" | "other" { // const ext = path.extname(filename).toLowerCase(); // if ([".jpg", ".jpeg", ".png", ".webp"].includes(ext)) return "image"; // if ([".pdf", ".doc", ".docx"].includes(ext)) return "document"; // return "other"; // } // // --- Helper: recursive walk dir --- // async function walkDir( // dir: string, // fileList: string[] = [] // ): Promise { // const entries = await fs.readdir(dir, { withFileTypes: true }); // for (const entry of entries) { // const fullPath = path.join(dir, entry.name); // if (entry.isDirectory()) { // if (entry.name === "__MACOSX") continue; // skip folder sampah // await walkDir(fullPath, fileList); // } else { // if (entry.name.startsWith(".") || entry.name === ".DS_Store") continue; // skip file sampah // fileList.push(fullPath); // } // } // return fileList; // } // export default async function seedAssets() { // console.log("🚀 Seeding assets..."); // console.log("📁 Upload dir:", UPLOADS_DIR); // await fs.mkdir(UPLOADS_DIR, { recursive: true }); // // 1. Download zip // const url = // "https://cld-dkr-makuro-seafile.wibudev.com/f/eadd52c5bd654ec789a3/?dl=1"; // const res = await fetchWithRetry(url, 3, 20000); // // Validasi content-type // const contentType = res.headers.get("content-type"); // if (!contentType?.includes("zip")) { // throw new Error(`Invalid content-type (${contentType}). Expected ZIP file`); // } // const buffer = Buffer.from(await res.arrayBuffer()); // // Validasi ukuran file // if (buffer.length < 100) { // throw new Error("Downloaded ZIP is empty or corrupted"); // } // // Validasi signature ZIP ("PK") // if (buffer.toString("utf8", 0, 2) !== "PK") { // throw new Error("Invalid ZIP signature (PK not found)"); // } // // 2. Extract zip ke folder tmp // const extractDir = path.join(process.cwd(), "tmp_assets"); // await fs.rm(extractDir, { recursive: true, force: true }); // await fs.mkdir(extractDir, { recursive: true }); // let zip: AdmZip; // try { // zip = new AdmZip(buffer); // } catch (err) { // throw new Error("Failed to parse ZIP file (corrupted or invalid)"); // } // try { // zip.extractAllTo(extractDir, true); // } catch (err) { // throw new Error("Failed to extract ZIP contents"); // } // // 3. Cari semua file valid (recursive) // const files = await walkDir(extractDir); // // 4. Loop tiap file & simpan // for (const filePath of files) { // const entryName = path.basename(filePath); // const category = detectCategory(entryName); // let finalName = entryName; // let mimeType = "application/octet-stream"; // let targetPath = ""; // if (category === "image") { // const fileBaseName = path.parse(entryName).name; // finalName = `${fileBaseName}.webp`; // targetPath = path.join(UPLOADS_DIR, "images", finalName); // await fs.mkdir(path.dirname(targetPath), { recursive: true }); // await sharp(filePath).webp({ quality: 80 }).toFile(targetPath); // mimeType = "image/webp"; // } else if (category === "document") { // targetPath = path.join(UPLOADS_DIR, "documents", entryName); // await fs.mkdir(path.dirname(targetPath), { recursive: true }); // await fs.copyFile(filePath, targetPath); // mimeType = "application/pdf"; // } else { // targetPath = path.join(UPLOADS_DIR, "other", entryName); // await fs.mkdir(path.dirname(targetPath), { recursive: true }); // await fs.copyFile(filePath, targetPath); // } // const existing = await prisma.fileStorage.findUnique({ // where: { name: finalName }, // }); // if (existing) { // // Restore kalau soft deleted // await prisma.fileStorage.update({ // where: { name: finalName }, // data: { // path: targetPath, // realName: entryName, // mimeType, // link: `/uploads/${category}/${finalName}`, // category, // deletedAt: null, // isActive: true, // }, // }); // console.log(`♻️ restored: ${category}/${finalName}`); // } else { // await prisma.fileStorage.create({ // data: { // name: finalName, // realName: entryName, // path: targetPath, // mimeType, // link: `/uploads/${category}/${finalName}`, // category, // }, // }); // console.log(`📂 created: ${category}/${finalName}`); // } // console.log(`📂 saved: ${category}/${finalName}`); // } // // 6. Cleanup // await fs.rm(extractDir, { recursive: true, force: true }); // console.log("✅ Selesai seed assets!"); // console.log("DB URL (asset):", process.env.DATABASE_URL); // } // // --- Auto run kalau dipanggil langsung --- // if (import.meta.main) { // seedAssets() // .catch((err) => { // console.error("❌ Error seeding assets:", err); // process.exit(1); // }) // .finally(async () => { // await prisma.$disconnect(); // }); // } // prisma/seedAssets.ts import prisma from "@/lib/prisma"; import AdmZip from "adm-zip"; import fs from "fs/promises"; import path from "path"; import sharp from "sharp"; import mime from "mime-types"; import fetchWithRetry from "./data/fetchWithRetry"; /* ========================= * CONFIG * ========================= */ const UPLOADS_DIR = path.resolve( process.env.WIBU_UPLOAD_DIR || "uploads" ); const TMP_DIR = path.join(process.cwd(), "tmp_assets"); const CATEGORY_DIR: Record = { image: "images", document: "documents", other: "other", }; type FileCategory = "image" | "document" | "other"; /* ========================= * HELPERS * ========================= */ function detectCategory(filename: string): FileCategory { const ext = path.extname(filename).toLowerCase(); if ([".jpg", ".jpeg", ".png", ".webp"].includes(ext)) return "image"; if ([".pdf", ".doc", ".docx", ".txt"].includes(ext)) return "document"; return "other"; } async function walkDir( dir: string, result: string[] = [] ): Promise { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (entry.name === "__MACOSX") continue; await walkDir(fullPath, result); } else { if (entry.name.startsWith(".") || entry.name === ".DS_Store") continue; result.push(fullPath); } } return result; } async function ensureDir(dir: string) { await fs.mkdir(dir, { recursive: true }); } /* ========================= * FILE PROCESSORS * ========================= */ async function processImage(filePath: string, entryName: string) { const baseName = path.parse(entryName).name; const finalName = `${baseName}.webp`; const targetDir = path.join(UPLOADS_DIR, CATEGORY_DIR.image); const targetPath = path.join(targetDir, finalName); await ensureDir(targetDir); await sharp(filePath).webp({ quality: 80 }).toFile(targetPath); return { finalName, targetPath, mimeType: "image/webp", }; } async function processNonImage( filePath: string, entryName: string, category: FileCategory ) { const targetDir = path.join(UPLOADS_DIR, CATEGORY_DIR[category]); const targetPath = path.join(targetDir, entryName); await ensureDir(targetDir); await fs.copyFile(filePath, targetPath); return { finalName: entryName, targetPath, mimeType: mime.lookup(entryName) || "application/octet-stream", }; } /* ========================= * MAIN * ========================= */ export default async function seedAssets() { console.log("🚀 Seeding assets..."); console.log("📁 Upload dir:", UPLOADS_DIR); await ensureDir(UPLOADS_DIR); /* ===== Download ZIP ===== */ const url = "https://cld-dkr-makuro-seafile.wibudev.com/f/eadd52c5bd654ec789a3/?dl=1"; const res = await fetchWithRetry(url, 3, 20000); if (!res.headers.get("content-type")?.includes("zip")) { throw new Error("Invalid ZIP content-type"); } const buffer = Buffer.from(await res.arrayBuffer()); if (buffer.length < 100 || buffer.toString("utf8", 0, 2) !== "PK") { throw new Error("Corrupted ZIP file"); } /* ===== Extract ===== */ await fs.rm(TMP_DIR, { recursive: true, force: true }); await ensureDir(TMP_DIR); const zip = new AdmZip(buffer); zip.extractAllTo(TMP_DIR, true); /* ===== Process Files ===== */ const files = await walkDir(TMP_DIR); for (const filePath of files) { const entryName = path.basename(filePath); const category = detectCategory(entryName); let result; if (category === "image") { result = await processImage(filePath, entryName); } else { result = await processNonImage(filePath, entryName, category); } const { finalName, targetPath, mimeType } = result; const existing = await prisma.fileStorage.findUnique({ where: { name: finalName }, }); const data = { name: finalName, realName: entryName, path: targetPath, mimeType, link: `/uploads/${CATEGORY_DIR[category]}/${finalName}`, category, deletedAt: null, isActive: true, }; if (existing) { await prisma.fileStorage.update({ where: { name: finalName }, data, }); console.log(`♻️ restored: ${category}/${finalName}`); } else { await prisma.fileStorage.create({ data }); console.log(`📂 created: ${category}/${finalName}`); } } /* ===== Cleanup ===== */ await fs.rm(TMP_DIR, { recursive: true, force: true }); console.log("✅ Selesai seed assets!"); } /* ===== Auto Run ===== */ if (import.meta.main) { seedAssets() .catch((err) => { console.error("❌ Error seeding assets:", err); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); }); }