93 lines
2.5 KiB
JavaScript
93 lines
2.5 KiB
JavaScript
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");
|
|
|
|
const ALLOWED_IMAGE_PREFIXES = ["/posts/image/", "/profiles/image/"];
|
|
|
|
function handleImageUpload(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 handlePostUpload(req, res, next) {
|
|
return handleImageUpload(req, res, next);
|
|
}
|
|
|
|
function handleProfileUpload(req, res, next) {
|
|
return handleImageUpload(req, res, next);
|
|
}
|
|
|
|
function imageLinkForFile(file, prefix = "/posts/image/") {
|
|
return file ? `${prefix}${file.filename}` : null;
|
|
}
|
|
|
|
function profileImageLinkForFile(file) {
|
|
return imageLinkForFile(file, "/profiles/image/");
|
|
}
|
|
|
|
async function removeImageLink(imageLink) {
|
|
if (!imageLink || !ALLOWED_IMAGE_PREFIXES.some((prefix) => imageLink.startsWith(prefix))) {
|
|
return;
|
|
}
|
|
const filename = path.basename(imageLink);
|
|
await fs.promises.unlink(path.join(uploadDirectory, filename)).catch((err) => {
|
|
if (err.code !== "ENOENT") throw err;
|
|
});
|
|
}
|
|
|
|
async function removeUploadedProfileFile(file) {
|
|
if (file) await removeImageLink(profileImageLinkForFile(file));
|
|
}
|
|
|
|
async function removeUploadedFile(file) {
|
|
if (file) await removeImageLink(imageLinkForFile(file));
|
|
}
|
|
|
|
module.exports = {
|
|
handlePostUpload,
|
|
handleProfileUpload,
|
|
imageLinkForFile,
|
|
profileImageLinkForFile,
|
|
removeImageLink,
|
|
removeUploadedFile,
|
|
removeUploadedProfileFile,
|
|
uploadDirectory,
|
|
};
|