const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); const multer = require("multer"); const uploadDirectory = path.resolve( process.env.UPLOAD_DIR || path.join(process.cwd(), "uploads"), ); const maxFileSize = Number.parseInt(process.env.MAX_UPLOAD_SIZE_BYTES, 10) || 5 * 1024 * 1024; const extensionByMimeType = { "image/jpeg": ".jpg", "image/png": ".png", "image/gif": ".gif", "image/webp": ".webp", }; fs.mkdirSync(uploadDirectory, { recursive: true }); const storage = multer.diskStorage({ destination: uploadDirectory, filename: (req, file, callback) => { callback(null, `${crypto.randomUUID()}${extensionByMimeType[file.mimetype]}`); }, }); const uploadImage = multer({ storage, limits: { fileSize: maxFileSize, files: 1 }, fileFilter: (req, file, callback) => { if (!extensionByMimeType[file.mimetype]) { return callback(new Error("Only JPEG, PNG, GIF, and WebP images are allowed")); } callback(null, true); }, }).single("image"); function handlePostUpload(req, res, next) { uploadImage(req, res, (err) => { if (!err) return next(); if (err.code === "LIMIT_FILE_SIZE") { return res.status(413).send("Image exceeds the maximum allowed size"); } return res.status(400).send(err.message); }); } function imageLinkForFile(file) { return file ? `/posts/image/${file.filename}` : null; } async function removeImageLink(imageLink) { if (!imageLink || !imageLink.startsWith("/posts/image/")) return; const filename = path.basename(imageLink); await fs.promises.unlink(path.join(uploadDirectory, filename)).catch((err) => { if (err.code !== "ENOENT") throw err; }); } async function removeUploadedFile(file) { if (file) await removeImageLink(imageLinkForFile(file)); } module.exports = { handlePostUpload, imageLinkForFile, removeImageLink, removeUploadedFile, uploadDirectory, };