S3 gallery

This commit is contained in:
2026-06-19 12:35:49 +03:30
parent 85c8107799
commit c73027540a
3 changed files with 276 additions and 206 deletions
+75 -203
View File
@@ -1,12 +1,20 @@
const Gallery = require("../models/Gallery"); const Gallery = require("../models/Gallery");
const { body, validationResult } = require("express-validator"); const { body, validationResult } = require("express-validator");
const sharp = require("sharp");
const crypto = require("crypto"); const crypto = require("crypto");
const { _sr } = require("../plugins/serverResponses"); const { _sr } = require("../plugins/serverResponses");
const { res404, res500 } = require("../plugins/controllersHelperFunctions"); const { res404, res500 } = require("../plugins/controllersHelperFunctions");
const fs = require("fs");
const moment = require("moment-jalaali"); const moment = require("moment-jalaali");
const Category = require("./../models/Category"); const Category = require("./../models/Category");
const {
deleteGalleryFile,
uploadGalleryFile,
uploadProcessedImage,
deleteAllGalleryFiles,
deleteGalleryCover,
getStoredFileName,
getStoredArray,
GALLERY_IMAGE_PREFIX,
} = require("../plugins/galleryImageStorage");
////// variables ////// variables
const picsWidth = 1920; const picsWidth = 1920;
@@ -45,139 +53,40 @@ const imageLimit = 2;
const graphicLimit = 5; const graphicLimit = 5;
const videoLimit = 20; const videoLimit = 20;
////// functions ////// functions
const _upload = async (fileType, file, galleryId, Hash) => { const _upload = async (fileType, file, galleryId) => {
try { if (fileType === "graphic") {
const fileName = `gallery_${galleryId}_${Date.now()}.${ return uploadGalleryFile("graphic", file, galleryId, {
file.mimetype.split("/")[1] mainQuality: graphicQuality,
}`; thumbWidth: thumbGraphicWidth,
const uploadPath = `./static/uploads/${fileType}s/gallery/${fileName}`; thumbHeight: thumbGraphicHeight,
const thumbUploadPath = `./static/uploads/${fileType}s/gallery/thumb_${fileName}`; thumbQuality: thumbGraphicQuality,
});
switch (fileType) {
case "image":
try {
const target = sharp(file.data);
await target
.resize(picsWidth, picsHeight, {
fit: "cover",
})
.toFormat("jpg")
.jpeg({
quality: picsQuality,
})
.toFile(uploadPath);
await target
.resize(thumbPicsWidth, thumbPicsHeight, {
fit: "cover",
})
.toFormat("jpg")
.jpeg({
quality: thumbPicsQuality,
})
.toFile(thumbUploadPath);
} catch (error) {
console.log(error);
}
break;
case "graphic":
try {
const target = sharp(file.data);
await target
// .resize(graphicWidth, graphicHeight, {
// fit: 'cover'
// })
.toFormat("jpg")
.jpeg({
quality: graphicQuality,
})
.toFile(uploadPath);
await target
.resize(thumbGraphicWidth, thumbGraphicHeight, {
fit: "cover",
})
.toFormat("jpg")
.jpeg({
quality: thumbGraphicQuality,
})
.toFile(thumbUploadPath);
} catch (error) {
console.log(error);
}
break;
case "video":
await file.mv(uploadPath);
break;
}
return fileName;
} catch (error) {
console.log(error);
return null;
} }
if (fileType === "video") {
return uploadGalleryFile("video", file, galleryId, {});
}
return uploadGalleryFile("image", file, galleryId, {
mainWidth: picsWidth,
mainHeight: picsHeight,
mainQuality: picsQuality,
thumbWidth: thumbPicsWidth,
thumbHeight: thumbPicsHeight,
thumbQuality: thumbPicsQuality,
});
}; };
const _deleteOne = async (fileType, name) => { const _deleteOne = async (fileType, name) => {
try { try {
let targetFile = `./static/uploads/${fileType}s/gallery/${name}`; await deleteGalleryFile(fileType, name);
let thumbTargetFile = `./static/uploads/${fileType}s/gallery/thumb_${name}`; return name;
if (fs.existsSync(targetFile)) {
fs.unlink(targetFile, (err) => {
if (err) console.log(err);
});
if (fileType !== "video") {
fs.unlink(thumbTargetFile, (err) => {
if (err) console.log(err);
});
}
return name;
} else {
return null;
}
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return null; return null;
} }
}; };
const _deleteAll = async (galleryId, galleryType) => {
try {
const targetDir = `./static/uploads/${galleryType}s/gallery/`;
const getFiles = fs.readdirSync(targetDir);
for (const file of getFiles) {
if (file.includes(galleryId)) {
fs.unlink(`./static/uploads/${galleryType}s/gallery/${file}`, (err) => {
if (err) console.log(err);
});
}
}
} catch (error) {
console.log(error);
}
};
const _deleteCover = async (galleryId) => {
try {
const targetDir = `./static/uploads/images/gallery/`;
const getFiles = fs.readdirSync(targetDir);
for (const file of getFiles) {
if (file.includes(galleryId) && file.includes("cover")) {
fs.unlink(`./static/uploads/images/gallery/${file}`, (err) => {
if (err) console.log(err);
});
}
}
} catch (error) {
console.log(err);
}
};
const _randomGallery = async (queryObject) => { const _randomGallery = async (queryObject) => {
let setSkip; let setSkip;
try { try {
@@ -599,7 +508,7 @@ module.exports.updateGallery = [
} }
if (targetGallery.galleryType !== galleryType) { if (targetGallery.galleryType !== galleryType) {
await _deleteAll(targetGallery._id, targetGallery.galleryType); await deleteAllGalleryFiles(targetGallery);
targetGallery.images = []; targetGallery.images = [];
targetGallery.thumbImages = []; targetGallery.thumbImages = [];
targetGallery.video = null; targetGallery.video = null;
@@ -722,8 +631,8 @@ module.exports.deletOneGallery = [
return res500(res, `شما اجازه پاک کردن این گالری را ندارید`); return res500(res, `شما اجازه پاک کردن این گالری را ندارید`);
} }
await _deleteAll(targetGallery._id, targetGallery.galleryType); await deleteAllGalleryFiles(targetGallery);
await _deleteCover(targetGallery._id); await deleteGalleryCover(targetGallery);
/** delete gallery */ /** delete gallery */
await targetGallery.deleteOne(); await targetGallery.deleteOne();
@@ -1037,27 +946,20 @@ module.exports.uploadPDF = [
targetGallery.thumbImages.push(`thumb_${fileName}`); targetGallery.thumbImages.push(`thumb_${fileName}`);
break; break;
case "video": case "video":
if ( if (getStoredFileName(targetGallery, "video")) {
targetGallery.video && await deleteGalleryFile(
(await fs.existsSync(`./static/${targetGallery?.video}`)) "video",
) { getStoredFileName(targetGallery, "video")
fs.unlink(`./static/${targetGallery.video}`, (err) => { );
if (err) return res.status(500).json({ msg: err.message });
});
} }
targetGallery.video = fileName; targetGallery.video = fileName;
break; break;
case "graphic": case "graphic":
if ( if (getStoredFileName(targetGallery, "graphic")) {
targetGallery.graphic && await deleteGalleryFile(
(await fs.existsSync(`./static/${targetGallery?.graphic}`)) "graphic",
) { getStoredFileName(targetGallery, "graphic")
fs.unlink(`./static/${targetGallery.graphic}`, (err) => { );
if (err) return res.status(500).json({ msg: err.message });
});
fs.unlink(`./static/${targetGallery.thumbGraphic}`, (err) => {
if (err) return res.status(500).json({ msg: err.message });
});
} }
targetGallery.graphic = fileName; targetGallery.graphic = fileName;
targetGallery.thumbGraphic = `thumb_${fileName}`; targetGallery.thumbGraphic = `thumb_${fileName}`;
@@ -1130,9 +1032,7 @@ module.exports.uploadFile = [
return res.status(404).json({ msg: `گالری پیدا نشد` }); return res.status(404).json({ msg: `گالری پیدا نشد` });
} }
const Hash = targetGallery.uidImage.map((path) => const Hash = getStoredArray(targetGallery, "uidImage");
path.replace("/uploads/images/gallery/", "")
);
if (Hash.includes(fileHash)) { if (Hash.includes(fileHash)) {
return res.status(200).json({ msg: `این فایل قبلا بارگذاری شده` }); return res.status(200).json({ msg: `این فایل قبلا بارگذاری شده` });
} }
@@ -1206,27 +1106,20 @@ module.exports.uploadFile = [
targetGallery.thumbImages.push(`thumb_${fileName}`); targetGallery.thumbImages.push(`thumb_${fileName}`);
break; break;
case "video": case "video":
if ( if (getStoredFileName(targetGallery, "video")) {
targetGallery.video && await deleteGalleryFile(
(await fs.existsSync(`./static/${targetGallery?.video}`)) "video",
) { getStoredFileName(targetGallery, "video")
fs.unlink(`./static/${targetGallery.video}`, (err) => { );
if (err) return res.status(500).json({ msg: err.message });
});
} }
targetGallery.video = fileName; targetGallery.video = fileName;
break; break;
case "graphic": case "graphic":
if ( if (getStoredFileName(targetGallery, "graphic")) {
targetGallery.graphic && await deleteGalleryFile(
(await fs.existsSync(`./static/${targetGallery?.graphic}`)) "graphic",
) { getStoredFileName(targetGallery, "graphic")
fs.unlink(`./static/${targetGallery.graphic}`, (err) => { );
if (err) return res.status(500).json({ msg: err.message });
});
fs.unlink(`./static/${targetGallery.thumbGraphic}`, (err) => {
if (err) return res.status(500).json({ msg: err.message });
});
} }
targetGallery.graphic = fileName; targetGallery.graphic = fileName;
targetGallery.thumbGraphic = `thumb_${fileName}`; targetGallery.thumbGraphic = `thumb_${fileName}`;
@@ -1743,47 +1636,26 @@ module.exports.uploadCover = [
const imageName = `cover_gallery_${Date.now()}_${targetGallery._id}.jpg`; const imageName = `cover_gallery_${Date.now()}_${targetGallery._id}.jpg`;
const thumbImg = `thumb_${imageName}`; const thumbImg = `thumb_${imageName}`;
try { try {
const target = sharp(file.data); await uploadProcessedImage(
file.data,
imageName,
coverWidth,
coverHeight,
coverQuality,
GALLERY_IMAGE_PREFIX
);
await target await uploadProcessedImage(
.resize(coverWidth, coverHeight, { file.data,
fit: "cover", thumbImg,
}) thumbCoverWidth,
.toFormat("jpg") thumbCoverHeight,
.jpeg({ thumbCoverQuality,
quality: coverQuality, GALLERY_IMAGE_PREFIX
}) );
.toFile(`./static/uploads/images/gallery/${imageName}`);
await target if (getStoredFileName(targetGallery, "cover")) {
.resize(thumbCoverWidth, thumbCoverHeight, { await deleteGalleryCover(targetGallery);
fit: "cover",
})
.toFormat("jpg")
.jpeg({
quality: thumbCoverQuality,
})
.toFile(`./static/uploads/images/gallery/${thumbImg}`);
/**
* if exist old pic delete it from file
*/
if (targetGallery.cover) {
let oldPic = `./static/uploads/images/gallery/${targetGallery.cover}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (err) => {
if (err) console.log("Error deleting old cover:", err);
});
}
}
if (targetGallery.thumbCover) {
let oldThumbPic = `./static/uploads/images/gallery/${targetGallery.thumbCover}`;
if (fs.existsSync(oldThumbPic)) {
fs.unlink(oldThumbPic, (err) => {
if (err) console.log("Error deleting old thumb cover:", err);
});
}
} }
// Update gallery record with new cover images // Update gallery record with new cover images
+10 -3
View File
@@ -1,16 +1,23 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2'); const mongoosePaginate = require('mongoose-paginate-v2');
const s3Storage = require('../plugins/s3Storage');
function galleryUrl(img, prefix) {
if (!img) return null;
if (img.startsWith('http://') || img.startsWith('https://')) return img;
return s3Storage.getPublicUrl(`${prefix}/${img}`);
}
function image(img) { function image(img) {
if (img) return '/uploads/images/gallery/' + img return galleryUrl(img, 'uploads/images/gallery');
} }
function graphic(img) { function graphic(img) {
if (img) return '/uploads/graphics/gallery/' + img return galleryUrl(img, 'uploads/graphics/gallery');
} }
function video(vdo) { function video(vdo) {
if (vdo) return '/uploads/videos/gallery/' + vdo return galleryUrl(vdo, 'uploads/videos/gallery');
} }
const GallerySchema = mongoose.Schema({ const GallerySchema = mongoose.Schema({
+191
View File
@@ -0,0 +1,191 @@
const sharp = require("sharp");
const s3Storage = require("./s3Storage");
const GALLERY_IMAGE_PREFIX = "uploads/images/gallery";
const GALLERY_GRAPHIC_PREFIX = "uploads/graphics/gallery";
const GALLERY_VIDEO_PREFIX = "uploads/videos/gallery";
const getPrefixForFileType = (fileType) => {
switch (fileType) {
case "graphic":
return GALLERY_GRAPHIC_PREFIX;
case "video":
return GALLERY_VIDEO_PREFIX;
default:
return GALLERY_IMAGE_PREFIX;
}
};
const getGalleryObjectKey = (fileType, fileName) =>
`${getPrefixForFileType(fileType)}/${fileName}`;
const getStoredFileName = (doc, field) =>
doc.get(field, null, { getters: false });
const getStoredArray = (doc, field) => {
const value = doc.get(field, null, { getters: false });
return Array.isArray(value) ? value : [];
};
const deleteGalleryFile = async (fileType, fileName) => {
if (!fileName) return;
const baseName = fileName.replace(/^thumb_/, "");
try {
await s3Storage.deleteObject(getGalleryObjectKey(fileType, baseName));
} catch (error) {
console.log(`Failed to delete ${baseName} from S3:`, error?.message);
}
if (fileType !== "video") {
try {
await s3Storage.deleteObject(
getGalleryObjectKey(fileType, `thumb_${baseName}`)
);
} catch (error) {
console.log(`Failed to delete thumb_${baseName} from S3:`, error?.message);
}
}
};
const uploadProcessedImage = async (
imageData,
imageName,
width,
height,
quality,
prefix = GALLERY_IMAGE_PREFIX
) => {
let pipeline = sharp(imageData);
if (width && height) {
pipeline = pipeline.resize(width, height, { fit: "cover" });
}
const buffer = await pipeline.jpeg({ quality }).toBuffer();
await s3Storage.uploadBuffer(
`${prefix}/${imageName}`,
buffer,
"image/jpeg"
);
return imageName;
};
const uploadGalleryVideo = async (fileData, fileName, contentType) => {
await s3Storage.uploadBuffer(
`${GALLERY_VIDEO_PREFIX}/${fileName}`,
fileData,
contentType
);
return fileName;
};
const uploadGalleryFile = async (fileType, file, galleryId, options = {}) => {
const fileName = `gallery_${galleryId}_${Date.now()}.${
file.mimetype.split("/")[1]
}`;
const prefix = getPrefixForFileType(fileType);
switch (fileType) {
case "image":
await uploadProcessedImage(
file.data,
fileName,
options.mainWidth,
options.mainHeight,
options.mainQuality,
prefix
);
await uploadProcessedImage(
file.data,
`thumb_${fileName}`,
options.thumbWidth,
options.thumbHeight,
options.thumbQuality,
prefix
);
break;
case "graphic":
await uploadProcessedImage(
file.data,
fileName,
null,
null,
options.mainQuality,
prefix
);
await uploadProcessedImage(
file.data,
`thumb_${fileName}`,
options.thumbWidth,
options.thumbHeight,
options.thumbQuality,
prefix
);
break;
case "video":
await uploadGalleryVideo(file.data, fileName, file.mimetype);
break;
}
return fileName;
};
const deleteAllGalleryFiles = async (gallery) => {
const galleryType = getStoredFileName(gallery, "galleryType");
if (galleryType === "image") {
const images = getStoredArray(gallery, "images");
for (const fileName of images) {
await deleteGalleryFile("image", fileName);
}
return;
}
if (galleryType === "graphic") {
await deleteGalleryFile("graphic", getStoredFileName(gallery, "graphic"));
return;
}
if (galleryType === "video") {
await deleteGalleryFile("video", getStoredFileName(gallery, "video"));
}
};
const deleteGalleryCover = async (gallery) => {
const cover = getStoredFileName(gallery, "cover");
const thumbCover = getStoredFileName(gallery, "thumbCover");
if (cover) {
try {
await s3Storage.deleteObject(`${GALLERY_IMAGE_PREFIX}/${cover}`);
} catch (error) {
console.log(`Failed to delete cover from S3:`, error?.message);
}
}
if (thumbCover) {
try {
await s3Storage.deleteObject(`${GALLERY_IMAGE_PREFIX}/${thumbCover}`);
} catch (error) {
console.log(`Failed to delete thumb cover from S3:`, error?.message);
}
}
};
module.exports = {
GALLERY_IMAGE_PREFIX,
GALLERY_GRAPHIC_PREFIX,
GALLERY_VIDEO_PREFIX,
getStoredFileName,
getStoredArray,
deleteGalleryFile,
uploadProcessedImage,
uploadGalleryFile,
deleteAllGalleryFiles,
deleteGalleryCover,
};