import { randomUUID } from "crypto"; import { mkdir, rm, writeFile } from "fs/promises"; import path from "path"; export const MEDIA_UPLOAD_ROOT = path.join(process.cwd(), "public", "uploads", "media"); export const MAX_MEDIA_FILE_SIZE = 5 * 1024 * 1024; const MIME_EXTENSIONS: Record = { "image/x-icon": ".ico", "image/vnd.microsoft.icon": ".ico", "image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp", "image/svg+xml": ".svg", "application/pdf": ".pdf", }; export function sanitizeBaseName(value: string) { return value .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 60); } export function getExtensionForMimeType(mimeType: string) { return MIME_EXTENSIONS[mimeType] ?? null; } export function isManagedMediaFilePath(filePath: string | null | undefined) { return typeof filePath === "string" && filePath.startsWith("/uploads/media/"); } export function resolveMediaUploadPath(filePath: string) { if (!isManagedMediaFilePath(filePath)) { throw new Error("Only managed media uploads can be resolved."); } const relativePath = filePath.replace("/uploads/media/", ""); const absolutePath = path.resolve(MEDIA_UPLOAD_ROOT, relativePath); if (!absolutePath.startsWith(MEDIA_UPLOAD_ROOT)) { throw new Error("Resolved media upload path escapes the upload root."); } return absolutePath; } export async function removeManagedMediaFile(filePath: string | null | undefined) { if (!isManagedMediaFilePath(filePath)) { return false; } const managedFilePath = filePath as string; await rm(resolveMediaUploadPath(managedFilePath), { force: true, }); return true; } export async function saveMediaUpload(file: File, folder: string) { if (!file || file.size === 0) { return null; } const extension = getExtensionForMimeType(file.type); if (!extension) { throw new Error("Unsupported file type."); } if (file.size > MAX_MEDIA_FILE_SIZE) { throw new Error("File is too large."); } const safeBaseName = sanitizeBaseName(file.name.replace(/\.[^.]+$/, "")) || "asset"; const finalName = `${Date.now()}-${safeBaseName}-${randomUUID().slice(0, 8)}${extension}`; const targetDir = path.join(MEDIA_UPLOAD_ROOT, folder); const targetPath = path.join(targetDir, finalName); await mkdir(targetDir, { recursive: true }); await writeFile(targetPath, Buffer.from(await file.arrayBuffer())); return { url: `/uploads/media/${folder}/${finalName}`, fileName: finalName, mimeType: file.type, size: file.size, }; }