I'm having some weird behaviour with an application written in node.js running inside a docker container.
It sucessfully uploads and delete images using a react-admin (https://marmelab.com/react-admin/) front-end.
-rw-r--r-- 1 root root 87.2K Feb 13 2021 60283f6b7b33b304a4b6b428.webp
-rw-r--r-- 1 root root 32.2K Dec 7 01:54 60283f6b7b33b304a4b6b428_1.jpg
It even replaces a previously uploaded image with a new one if the extension is different.
-rw-r--r-- 1 root root 87.2K Feb 13 2021 60283f6b7b33b304a4b6b428.webp
-rw-r--r-- 1 root root 674.1K Dec 7 02:26 60283f6b7b33b304a4b6b428_1.png
But for some reason, if I upload an image, a totally different one than a previously uploaded, but with the same extension, resulting in a image with the same name than the first deleted image, then the said image will show, instead of the newer one.
-rw-r--r-- 1 root root 87.2K Feb 13 2021 60283f6b7b33b304a4b6b428.webp
-rw-r--r-- 1 root root 32.2K Dec 7 01:54 60283f6b7b33b304a4b6b428_1.jpg
The said application uploads files using multer as a middleware:
const multer = require("multer");
const path = require("path");
const {
PATH_PRODUCT_TEMP_IMAGE
} = require("../services/config");
var storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, PATH_PRODUCT_TEMP_IMAGE),
filename: (req, file, cb) => {
if (file.fieldname === "picture")
cb(null, `${req.params.id}${path.extname(file.originalname)}`);
else if (file.fieldname === "picture1")
cb(null, `${req.params.id}_1${path.extname(file.originalname)}`);
},
});
// Filter files with multer
const multerFilter = (req, file, cb) => {
if (file.mimetype.startsWith("image")) {
cb(null, true);
} else {
cb("Not an image! Please upload only images.", false);
}
};
const maxFileUploadSizeMb = 15;
var upload = multer({
storage: storage,
limits: { fileSize: maxFileUploadSizeMb * 1024 * 1024 },
fileFilter: multerFilter,
});
module.exports = upload;
The API controller that manages the file upload is:
router.put(
"/:id",
[
auth,
upload.fields([
{ name: "picture", maxCount: 1 },
{ name: "picture1", maxCount: 1 },
]),
],
async (req, res) => {
try {
let objToUpdate = buildProduct(req.body);
const product = await Product.findById(req.params.id);
if (!product) throw new Error(`Product ${req.params.id} doesn't exist`);
if (req.files.picture?.length > 0) {
objToUpdate = {
...objToUpdate,
primaryImage: req.files.picture[0].filename,
};
removeProductImage(product.primaryImage);
resizeProductImage(objToUpdate.primaryImage);
}
if (req.files.picture1?.length > 0) {
objToUpdate = {
...objToUpdate,
image1: req.files.picture1[0].filename,
};
removeProductImage(product?.image1);
resizeProductImage(objToUpdate.image1);
}
await product.updateOne(objToUpdate);
res.send(product);
} catch (error) {
sendError500(res, error);
}
}
);
const removeProductImage = async (imageName) => {
if (notNullOrEmpty(imageName))
return await removeFile(path.join(PATH_PRODUCT_IMAGE, imageName));
};
const removeFile = async (filePathName) => {
let result = false;
try {
await fs.unlink(filePathName, (error) => {
if (error) throwError(error);
else result = true;
});
} catch (error) {
throwError(error);
}
return result;
function throwError(error) {
throw new Error(`Can't delete file: ${filePathName} - ${error.message}`);
}
};
The entire project is running in docker using named volumes as an storage for images. Nevertheless using the same code base but working with bind mount, it works as expected.
EDIT: I've noticed that I forgot to publish a function involved in the process:
const resizeProductImage = async (imageName) => {
if (!notNullOrEmpty(imageName)) return;
const imageTemp = path.join(PATH_PRODUCT_TEMP_IMAGE, imageName);
const imageFinal = path.join(PATH_PRODUCT_IMAGE, imageName);
await resizeImage({
imageFinal,
imageTemp,
imageFinalSize: +IMAGE_SIZE_PRODUCT,
});
await removeProductTempImage(imageName);
};
const resizeImage = async ({
imageFinal,
imageTemp,
imageFinalSize = 1024,
}) => {
try {
switch (path.extname(imageTemp)) {
case ".png":
await sharp(imageTemp)
.resize(imageFinalSize, null)
.png({ adaptiveFiltering: true })
.toFile(imageFinal, null);
break;
case ".webp":
case ".jpg":
case ".jpeg":
default:
await sharp(imageTemp)
.resize(imageFinalSize, null)
.toFile(imageFinal, null);
break;
}
} catch (error) {
try {
await fs.rename(imageTemp, imageFinal);
throw Error(
`Can't resize image ${imageTemp}, moving directly to ${imageFinal}, ${error.message}`
);
} catch (error) {
throw Error(
`Can't resize image ${imageTemp}, neither move to ${imageFinal}, ${error.message}`
);
}
}
};