diff --git a/pages/_portal/shafaf/index.vue b/pages/_portal/shafaf/index.vue
index 0acf506..b7b8386 100644
--- a/pages/_portal/shafaf/index.vue
+++ b/pages/_portal/shafaf/index.vue
@@ -36,7 +36,7 @@
v-for="(item, i) in items"
:key="i"
class="item">
-
+
diff --git a/server/controllers/categoryController.js b/server/controllers/categoryController.js
index c934ec8..e3f56f6 100644
--- a/server/controllers/categoryController.js
+++ b/server/controllers/categoryController.js
@@ -8,13 +8,19 @@ const {
checkValidations,
} = require("../plugins/controllersHelperFunctions");
const News = require("../models/News");
-const sharp = require("sharp");
-const fs = require("fs");
const { extname } = require("path");
+const {
+ deleteCategoryImage,
+ deleteShafafiatIcon,
+ uploadProcessedImage,
+ uploadShafafiatIcon,
+ uploadTermsFile,
+ deleteTermsFile,
+ getStoredImageName,
+} = require("../plugins/categoryImageStorage");
// variables
const titleFilterRegex = new RegExp("/", "g");
-const noCover = "/img/no-cover.jpg";
const coverNewsWidth = 400;
const coverNewsHeight = 400;
const coverQuality = 100;
@@ -411,34 +417,13 @@ module.exports.addCategory = [
image.mimetype.split("/")[1]
}`;
try {
- const target = sharp(image.data);
- await target
- .resize(coverNewsWidth, coverNewsHeight, { fit: "cover" })
- .jpeg({ quality: coverQuality })
- .toFile(`./static/uploads/images/category/${imageName}`);
-
- /**
- * if exist old pic delete it from file
- */
- // if (targetNews.cover !== noCover && !targetNews.imported) {
- // let oldPic = `./static${targetNews.cover}`;
- //
- // if (await fs.existsSync(oldPic)) {
- // fs.unlink(oldPic, (err) => {
- // if (err) return res500(res, err?.message);
- // });
- // }
- // }
-
- // if (targetNews.thumbCover !== noCover && !targetNews.imported) {
- // let oldPic = `./static${targetNews.thumbCover}`;
- //
- // if (await fs.existsSync(oldPic)) {
- // fs.unlink(oldPic, (err) => {
- // if (err) return res500(res, err?.message);
- // });
- // }
- // }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ coverNewsWidth,
+ coverNewsHeight,
+ coverQuality
+ );
create.managerCover = imageName;
await create.save();
@@ -461,33 +446,12 @@ module.exports.addCategory = [
const ext = extname(image.name);
const imageName = `shafafiat_icon_${Date.now()}${ext}`;
try {
- await image.mv(
- `${process.cwd()}/static/uploads/images/category/shafafiat/${imageName}`
+ await uploadShafafiatIcon(
+ image.data,
+ imageName,
+ image.mimetype
);
- /**
- * if exist old pic delete it from file
- */
- // if (targetNews.cover !== noCover && !targetNews.imported) {
- // let oldPic = `./static${targetNews.cover}`;
- //
- // if (await fs.existsSync(oldPic)) {
- // fs.unlink(oldPic, (err) => {
- // if (err) return res500(res, err?.message);
- // });
- // }
- // }
-
- // if (targetNews.thumbCover !== noCover && !targetNews.imported) {
- // let oldPic = `./static${targetNews.thumbCover}`;
- //
- // if (await fs.existsSync(oldPic)) {
- // fs.unlink(oldPic, (err) => {
- // if (err) return res500(res, err?.message);
- // });
- // }
- // }
-
create.icon = imageName;
await create.save();
} catch (e) {
@@ -812,24 +776,15 @@ module.exports.updateCategory = [
image.mimetype.split("/")[1]
}`;
try {
- const target = sharp(image.data);
- await target
- .resize(coverNewsWidth, coverNewsHeight, { fit: "cover" })
- .jpeg({ quality: coverQuality })
- .toFile(`./static/uploads/images/category/${imageName}`);
+ await deleteCategoryImage(targetCategory, "managerCover");
- /**
- * if exist old pic delete it from file
- */
- if (targetCategory.managerCover !== noCover) {
- let oldPic = `./static${targetCategory.managerCover}`;
-
- if (await fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ coverNewsWidth,
+ coverNewsHeight,
+ coverQuality
+ );
targetCategory.managerCover = imageName;
} catch (e) {
@@ -839,7 +794,6 @@ module.exports.updateCategory = [
if (req.files?.icon) {
const image = req.files.icon;
- console.log(image);
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) {
return res.status(422).json({
validation: {
@@ -852,30 +806,17 @@ module.exports.updateCategory = [
const ext = extname(image.name);
const imageName = `shafafiat_icon_${Date.now()}${ext}`;
try {
- await image.mv(
- `${process.cwd()}/static/uploads/images/category/shafafiat/${imageName}`
+ await deleteShafafiatIcon(
+ getStoredImageName(targetCategory, "icon")
);
- // const target = sharp(image.data)
- // await target
- // .resize(coverNewsWidth, coverNewsHeight, {fit: 'cover'})
- // .jpeg({quality: coverQuality})
- // .toFile(`./static/uploads/images/category/shafafiat/${imageName}`);
- /**
- * if exist old pic delete it from file
- */
- if (targetCategory.icon !== noCover) {
- let oldPic = `./static${targetCategory.icon}`;
-
- if (await fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadShafafiatIcon(
+ image.data,
+ imageName,
+ image.mimetype
+ );
targetCategory.icon = imageName;
- console.log(targetCategory);
} catch (e) {
return res500(res, e?.message);
}
@@ -1223,52 +1164,59 @@ module.exports.getParent = [
module.exports.addTermsFileToCategory = [
[body("termsTitle").notEmpty().withMessage(_sr["fa"].required.title)],
checkValidations(validationResult),
- (req, res) => {
- if (!req.files?.file)
- return res
- .status(422)
- .json({ validation: { file: { msg: _sr["fa"].required.file } } });
- if (req.files?.file.mimetype.split("/")[1] !== "pdf")
- return res
- .status(422)
- .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } });
+ async (req, res) => {
+ try {
+ if (!req.files?.file)
+ return res
+ .status(422)
+ .json({ validation: { file: { msg: _sr["fa"].required.file } } });
+ if (req.files?.file.mimetype.split("/")[1] !== "pdf")
+ return res
+ .status(422)
+ .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } });
- const data = { title: req.body.termsTitle };
- const file = req.files.file;
- const fileName =
- req.body.termsTitle.replace(titleFilterRegex, "-") +
- "_" +
- Date.now() +
- ".pdf";
- file.mv(`./static/uploads/files/terms/${fileName}`);
- data.file = fileName;
+ const category = await Category.findById(req.params.categoryId);
+ if (!category) return res404(res, "category not found");
- Category.findById(req.params.categoryId, (err, category) => {
- if (err || !category) return res404(res, err);
- category.terms.push(data);
- category.save((err) => {
- if (err) return res500(res, err);
- else return res.json({ message: _sr["fa"].response.success_save });
+ const fileName =
+ req.body.termsTitle.replace(titleFilterRegex, "-") +
+ "_" +
+ Date.now() +
+ ".pdf";
+
+ await uploadTermsFile(req.files.file.data, fileName);
+
+ category.terms.push({
+ title: req.body.termsTitle,
+ file: fileName,
});
- });
+ await category.save();
+
+ return res.json({ message: _sr["fa"].response.success_save });
+ } catch (err) {
+ return res500(res, err);
+ }
},
];
module.exports.removeTermsFileFromCategory = [
- (req, res) => {
- Category.findOne({ "terms._id": req.params.fileId }, (err, category) => {
- if (err || !category) return res404(res, err);
- const file = category.terms.id(req.params.fileId);
- if (!file) return res404(res, "not found");
- file.remove();
- fs.unlink(`./static/${file.file}`, (err) => {
- if (err) console.log(err);
- });
- category.save((err) => {
- if (err) return res500(res, err);
- else return res.json({ message: _sr["fa"].response.success_remove });
- });
- });
+ async (req, res) => {
+ try {
+ const category = await Category.findOne({ "terms._id": req.params.fileId });
+ if (!category) return res404(res, "not found");
+
+ const termsFile = category.terms.id(req.params.fileId);
+ if (!termsFile) return res404(res, "not found");
+
+ const fileName = getStoredImageName(termsFile, "file");
+ termsFile.remove();
+ await deleteTermsFile(fileName);
+ await category.save();
+
+ return res.json({ message: _sr["fa"].response.success_remove });
+ } catch (err) {
+ return res500(res, err);
+ }
},
];
diff --git a/server/controllers/contentController.js b/server/controllers/contentController.js
index 1d38889..c421fd7 100644
--- a/server/controllers/contentController.js
+++ b/server/controllers/contentController.js
@@ -1,9 +1,11 @@
const {body, validationResult} = require('express-validator');
-const sharp = require('sharp');
const {_sr} = require('../plugins/serverResponses');
const {res404, res500, hasWhiteSpaces, isLatinCharactersWithSymbol, generateRandomDigits} = require('../plugins/controllersHelperFunctions');
const Content = require('../models/Content');
-const fs = require('fs');
+const {
+ deleteContentImages,
+ uploadProcessedImage,
+} = require('../plugins/contentImageStorage');
////// variables
@@ -85,26 +87,23 @@ module.exports.addContent = [
const imageName = `manager_${Date.now()}_${create._id}.${image.mimetype.split('/')[1]}`;
const thumbName = 'thumb_' + imageName
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/content/${imageName}`)
- await target
- .resize(profileThumbWidth, profileThumbHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileThumbQuality
- })
- .toFile(`./static/uploads/images/content/${thumbName}`)
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
+ await uploadProcessedImage(
+ image.data,
+ thumbName,
+ profileThumbWidth,
+ profileThumbHeight,
+ profileThumbQuality
+ );
create.cover = imageName;
- create.thumb = imageName;
+ create.thumb = thumbName;
await create.save();
} catch (e) {
@@ -180,42 +179,22 @@ module.exports.updateContent = [
const imageName = `manager_${Date.now()}_${targetContent._id}.${image.mimetype.split('/')[1]}`;
const thumbName = 'thumb_' + imageName
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/content/${imageName}`)
- await target
- .resize(profileThumbWidth, profileThumbHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileThumbQuality
- })
- .toFile(`./static/uploads/images/content/${thumbName}`)
+ await deleteContentImages(targetContent);
- /**
- * if exist old pic delete it from file
- */
- if (targetContent.cover) {
- const oldPic = `./static${targetContent.cover}`
- const oldThumb = `./static${targetContent.thumb}`
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- if (fs.existsSync(oldThumb)) {
- fs.unlink(oldThumb, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
+ await uploadProcessedImage(
+ image.data,
+ thumbName,
+ profileThumbWidth,
+ profileThumbHeight,
+ profileThumbQuality
+ );
targetContent.cover = imageName;
targetContent.thumb = thumbName;
diff --git a/server/controllers/dataBackupController.js b/server/controllers/dataBackupController.js
index acc791b..7e4c6b1 100644
--- a/server/controllers/dataBackupController.js
+++ b/server/controllers/dataBackupController.js
@@ -1,9 +1,15 @@
const fs = require('fs')
+const {PassThrough} = require('stream')
const archiver = require('archiver')
const moment = require('moment')
const jwt = require('jsonwebtoken')
const {secretKey} = require('../authentication')
const {_sr} = require('../plugins/serverResponses')
+const {
+ appendS3UploadsToArchive,
+ uploadBackupArchive,
+ deleteOldBackups,
+} = require('../plugins/backupStorage')
// models
const Category = require('../models/Category')
const Comment = require('../models/Comment')
@@ -21,6 +27,8 @@ const Slider = require('../models/Slider')
const User = require('../models/User')
const YearBanner = require('../models/YearBanner')
+const hostFullError = '
فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.
'
+
module.exports.createBackUp = [
// check user token and permission to backup
async (req, res, next) => {
@@ -85,86 +93,72 @@ module.exports.createBackUp = [
const [categories, comments, contactus, contents, galleries, homepage, links, meetings, news, newsFiles, services, sliders, users, yearBanner, occasion] = await Promise.all(dataBaseQueries)
- if (await !fs.existsSync('./backup/database')) await fs.mkdirSync('./backup/database')
-
- await Promise.all([
- fs.writeFileSync(`./backup/database/categories_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(categories)),
- fs.writeFileSync(`./backup/database/comments_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(comments)),
- fs.writeFileSync(`./backup/database/contactus_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(contactus)),
- fs.writeFileSync(`./backup/database/contents_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(contents)),
- fs.writeFileSync(`./backup/database/galleries_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(galleries)),
- fs.writeFileSync(`./backup/database/homepage_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(homepage)),
- fs.writeFileSync(`./backup/database/links_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(links)),
- fs.writeFileSync(`./backup/database/meetings_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(meetings)),
- fs.writeFileSync(`./backup/database/news_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(news)),
- fs.writeFileSync(`./backup/database/newsFiles_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(newsFiles)),
- fs.writeFileSync(`./backup/database/services_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(services)),
- fs.writeFileSync(`./backup/database/sliders_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(sliders)),
- fs.writeFileSync(`./backup/database/users_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(users)),
- fs.writeFileSync(`./backup/database/yearBanner_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(yearBanner)),
- fs.writeFileSync(`./backup/database/occasion_${moment(Date.now()).format('YYYY-MM-DD')}.json`, JSON.stringify(occasion))
- ])
-
- /////////////////// create backup zip file
-
- const backupName = 'backup_' + moment(Date.now()).format('YYYY-MM-DD') + '.zip'
- // create a file to stream archive data to.
- const output = fs.createWriteStream(`./backup/${backupName}`)
+ const dateStr = moment(Date.now()).format('YYYY-MM-DD')
+ const backupName = `backup_${dateStr}.zip`
const archive = archiver('zip', {
- zlib: {level: 9} // Sets the compression level.
+ zlib: {level: 9}
})
+ const output = new PassThrough()
+ const chunks = []
+ output.on('data', (chunk) => chunks.push(chunk))
- // listen for all archive data to be written
- // 'close' event is fired only when a file descriptor is involved
- output.on('close', function () {
- console.log(archive.pointer() + ' total bytes')
- console.log('archiver has been finalized and the output file descriptor has closed.')
- return res.download(`./backup/${backupName}`)
- })
-
- // This event is fired when the data source is drained no matter what was the data source.
- // It is not part of this library but rather from the NodeJS Stream API.
- // @see: https://nodejs.org/api/stream.html#stream_event_end
- output.on('end', function () {
- console.log('Data has been drained')
- return res.status(500).send('
فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.
')
- })
-
- // good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', function (err) {
-
if (err.code === 'ENOENT') {
- // log warning
console.log('On archiver warning -- ENOENT error code', err)
} else {
- // throw error
console.log('On archiver warning -- other error codes ', err)
}
- return res.status(500).send('
فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.
')
+ return res.status(500).send(hostFullError)
})
- // good practice to catch this error explicitly
archive.on('error', function (err) {
- // throw err
console.log(err)
return res.status(500).json({message: 'مشکلی پیش آمده'})
})
- // pipe archive data to the file
- await archive.pipe(output)
+ archive.pipe(output)
- // append files from a sub-directory and naming it `new-subdir` within the archive
- await archive.directory(`./static/uploads/`, 'uploads')
- await archive.directory(`./backup/database/`, 'database')
+ const databaseExports = [
+ ['categories', categories],
+ ['comments', comments],
+ ['contactus', contactus],
+ ['contents', contents],
+ ['galleries', galleries],
+ ['homepage', homepage],
+ ['links', links],
+ ['meetings', meetings],
+ ['news', news],
+ ['newsFiles', newsFiles],
+ ['services', services],
+ ['sliders', sliders],
+ ['users', users],
+ ['yearBanner', yearBanner],
+ ['occasion', occasion],
+ ]
+
+ for (const [name, data] of databaseExports) {
+ archive.append(JSON.stringify(data), {name: `database/${name}_${dateStr}.json`})
+ }
+
+ await appendS3UploadsToArchive(archive)
- // finalize the archive (ie we are done appending files but streams have to finish yet)
- // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
await archive.finalize()
- await fs.rmdirSync('./backup/database', {recursive: true})
+ await new Promise((resolve, reject) => {
+ output.on('end', resolve)
+ output.on('error', reject)
+ })
+
+ const buffer = Buffer.concat(chunks)
+
+ await uploadBackupArchive(buffer, backupName)
+
+ res.setHeader('Content-Type', 'application/zip')
+ res.setHeader('Content-Disposition', `attachment; filename="${backupName}"`)
+ return res.send(buffer)
} catch (e) {
- return res.status(500).send('
فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.
')
+ return res.status(500).send(hostFullError)
}
}
]
@@ -173,12 +167,18 @@ module.exports.createBackUp = [
module.exports.removeOldBackups = [
async (req, res) => {
try {
- const files = await fs.readdirSync('./backup')
- for await (const file of files) {
- const isDirectory = await fs.statSync(`./backup/${file}`).isDirectory()
- if (isDirectory) await fs.rmdirSync(`./backup/${file}`, {recursive: true})
- else await fs.unlinkSync(`./backup/${file}`)
+ await deleteOldBackups()
+
+ if (fs.existsSync('./backup')) {
+ const files = fs.readdirSync('./backup')
+ for (const file of files) {
+ const filePath = `./backup/${file}`
+ const isDirectory = fs.statSync(filePath).isDirectory()
+ if (isDirectory) fs.rmdirSync(filePath, {recursive: true})
+ else fs.unlinkSync(filePath)
+ }
}
+
return res.json({message: _sr['fa'].response.success_remove})
} catch (e) {
return res.status(500).json({message: e})
diff --git a/server/controllers/electionController.js b/server/controllers/electionController.js
index 4bc9f84..0f94486 100644
--- a/server/controllers/electionController.js
+++ b/server/controllers/electionController.js
@@ -10,8 +10,11 @@ const {
hasWhiteSpaces,
isLatinCharactersWithSymbol
} = require('../plugins/controllersHelperFunctions');
-const fs = require('fs');
-const sharp = require('sharp');
+const {
+ deleteCandidateImage,
+ deleteElectionCandidateImages,
+ uploadProcessedImage,
+} = require('../plugins/electionImageStorage');
////// variables
const profileWidth = 300;
@@ -292,17 +295,7 @@ module.exports.deletOneElection = [
}
if (deleteElection.candidates.length) {
- const targetDir = `./static/uploads/images/election/`;
-
- const getFiles = fs.readdirSync(targetDir);
-
- for (const file of getFiles) {
- if (file.includes(deleteElection._id)) {
- fs.unlink(`./static/uploads/images/election/${file}`, (err) => {
- if (err) console.log(err)
- });
- }
- }
+ await deleteElectionCandidateImages(deleteElection);
}
await deleteElection.deleteOne();
@@ -457,16 +450,13 @@ module.exports.addCandidate = [
}
const imageName = 'candidate_' + Date.now() + '_' + targetElection._id + '.jpg';
data.profilePic = imageName;
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .toFormat('jpg')
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/election/${imageName}`)
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
targetElection.candidates.push(data);
@@ -528,29 +518,16 @@ module.exports.updateCandidate = [
return res.status(422).json({validation: {profilePic: {msg: _sr['fa'].format.image}}});
}
const imageName = 'candidate_' + Date.now() + '_' + targetElection._id + '.jpg';
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .toFormat('jpg')
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/election/${imageName}`)
+ await deleteCandidateImage(candidate);
- /**
- * if exist old pic delete it from file
- */
- if (candidate.profilePic) {
- const oldPic = `./static${candidate.profilePic}`;
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
candidate.profilePic = imageName;
}
@@ -586,15 +563,7 @@ module.exports.deleteCandidate = [
if (!candidate) return res404(res, `کاندید مورد نظر پیدا نشد`);
- if (candidate.profilePic) {
- const oldPic = `./static${candidate.profilePic}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await deleteCandidateImage(candidate);
await targetElection.candidates.id(candidateId).remove();
diff --git a/server/controllers/linksController.js b/server/controllers/linksController.js
index 3e3902a..e282d68 100644
--- a/server/controllers/linksController.js
+++ b/server/controllers/linksController.js
@@ -3,7 +3,6 @@ const {
body,
validationResult
} = require('express-validator');
-const sharp = require('sharp');
const {
_sr
} = require('../plugins/serverResponses');
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol,
generateRandomDigits
} = require('../plugins/controllersHelperFunctions');
-const fs = require('fs');
+const {
+ deleteLinksImage,
+ uploadProcessedImage,
+} = require('../plugins/linksImageStorage');
////// variables
const profileWidth = 50;
@@ -108,15 +110,13 @@ module.exports.addLinks = [
}
const imageName = `links_${Date.now()}_${createLinks._id}.${image.mimetype.split('/')[1]}`;
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/links/${imageName}`);
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
createLinks.icon = imageName;
@@ -243,28 +243,15 @@ module.exports.updateLinks = [
}
const imageName = `links_${Date.now()}_${targetLinks._id}.${image.mimetype.split('/')[1]}`;
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/links/${imageName}`);
+ await deleteLinksImage(targetLinks, 'icon');
- /**
- * if exist old pic delete it from file
- */
- if (targetLinks.icon) {
- var oldPic = `./static${targetLinks.icon}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
targetLinks.icon = imageName;
targetLinks.linksStatus = true;
@@ -312,15 +299,7 @@ module.exports.deletOneLinks = [
return res500(res, `شما اجازه حذف این پیوند را ندارید`);
}
- if (targetLinks.icon) {
- var oldPic = `./static${targetLinks.icon}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (error) => {
- if (error) return res500(res, error?.message);
- });
- }
- }
+ await deleteLinksImage(targetLinks, 'icon');
/** delete links */
await targetLinks.deleteOne();
diff --git a/server/controllers/occasionController.js b/server/controllers/occasionController.js
index c368fe4..8d42ae3 100644
--- a/server/controllers/occasionController.js
+++ b/server/controllers/occasionController.js
@@ -2,8 +2,10 @@ const Occasion = require('../models/Occasion');
const {body, validationResult} = require('express-validator');
const {_sr} = require('../plugins/serverResponses');
const {res404, res500} = require('../plugins/controllersHelperFunctions');
-const fs = require('fs');
-const sharp = require('sharp');
+const {
+ deleteOccasionImage,
+ uploadProcessedImage,
+} = require('../plugins/occasionImageStorage');
const moment = require('moment-jalaali');
////// variables
@@ -63,16 +65,13 @@ module.exports.addOccasion = [
}
const imageName = `occasion_${addOccasion._id}_${Date.now()}.jpg`;
- const target = sharp(image.data)
- await target
- .resize(occasionWidth, occasionHeight, {
- fit: 'cover'
- })
- .toFormat('jpg')
- .jpeg({
- quality: occasionQuality
- })
- .toFile(`./static/uploads/images/occasion/${imageName}`);
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ occasionWidth,
+ occasionHeight,
+ occasionQuality
+ );
addOccasion.status = true;
addOccasion.mainCover = imageName;
@@ -132,26 +131,15 @@ module.exports.updateOccasion = [
}
const imageName = `occasion_${targetOccasion._id}_${Date.now()}.jpg`;
- const target = sharp(image.data)
- await target
- .resize(occasionWidth, occasionHeight, {
- fit: 'cover'
- })
- .toFormat('jpg')
- .jpeg({
- quality: occasionQuality
- })
- .toFile(`./static/uploads/images/occasion/${imageName}`);
+ await deleteOccasionImage(targetOccasion, 'mainCover');
- if (targetOccasion.mainCover) {
- var oldPic = `./static${targetOccasion.mainCover}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ occasionWidth,
+ occasionHeight,
+ occasionQuality
+ );
targetOccasion.status = true;
targetOccasion.mainCover = imageName;
@@ -184,15 +172,7 @@ module.exports.deleteOccasion = [
return res404(res, `مناسبت مورد نظر پیدا نشد`);
}
- if (targetOccasion.mainCover) {
- var oldPic = `./static${targetOccasion.mainCover}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await deleteOccasionImage(targetOccasion, 'mainCover');
await targetOccasion.deleteOne();
diff --git a/server/controllers/pdfController.js b/server/controllers/pdfController.js
index 1d1d971..9aae71c 100644
--- a/server/controllers/pdfController.js
+++ b/server/controllers/pdfController.js
@@ -1,109 +1,148 @@
+const fs = require("fs/promises");
+const os = require("os");
+const path = require("path");
+const axios = require("axios");
const { body, validationResult } = require("express-validator");
const { _sr } = require("../plugins/serverResponses");
const { checkValidations } = require("../plugins/controllersHelperFunctions");
const Book = require("../models/Book");
-var convertapi = require('convertapi')('6zZB0nquI5V3P3Vg');
+const {
+ uploadPdf,
+ uploadBookSlide,
+ deletePdf,
+ deleteBookSlide,
+} = require("../plugins/bookImageStorage");
+const convertapi = require("convertapi")("6zZB0nquI5V3P3Vg");
module.exports.AddPDF = [
- [
- body('title')
- .notEmpty().withMessage(_sr['fa'].required.title)
- ],
- checkValidations(validationResult),
- async (req, res) => {
- try {
- const firstbook = [];
-
- if (!req.files?.file) {
- return res.status(422).json({ validation: { file: { msg: _sr['fa'].required.file } } });
- }
-
- if (req.files?.file.mimetype.split('/')[1] !== 'pdf') {
- return res.status(422).json({ validation: { file: { msg: 'فقط فرمت PDF قابل قبول است' } } });
- }
-
- const book = await Book.findById(req.body.bookId);
- const bookId = book ? book._id : null;
-
- const data = { title: req.body.title };
- const file = req.files.file;
- const fileName = req.body.title.replace(Date.now(), '-') + '_' + Date.now() + '.pdf';
- const imageName = req.body.title.replace(Date.now(), '-') + '_' + Date.now();
-
- const filePath = `./static/uploads/pdf/${fileName}`;
-
- await file.mv(filePath);
- data.file = fileName;
-
- const convertPromise = convertapi.convert('jpg', {
- File: filePath
- }, 'pdf');
-
- const result = await convertPromise;
- const savedFiles = await result.saveFiles(`./static/uploads/books/${imageName}.jpeg`);
-
- const imagePath = `https://ostan-mr.ir/uploads/books/${imageName}.jpeg`;
- firstbook.push(imagePath);
-
- if (bookId) {
- const update = await Book.findByIdAndUpdate(
- {_id:bookId},
- { $push: { slides: imagePath } },
- { upsert: true }
- );
- return res.status(201).json({ msg: "اسلاید کتاب اضافه شد", Data: update });
- } else {
- const newBook = new Book({
- title: req.body.title,
- uri: `https://ostan-mr.ir/uploads/pdf/${fileName}`,
- slides: firstbook
- });
- await newBook.save();
- return res.status(201).json({ msg: "کتاب ایجاد شد", Data: newBook });
- }
-
- } catch (error) {
- console.error(error);
- return res.status(500).json({ msg: "خطایی در پردازش فایل رخ داد", error: error.message });
- }
+ [body("title").notEmpty().withMessage(_sr["fa"].required.title)],
+ checkValidations(validationResult),
+ async (req, res) => {
+ let tmpDir;
+
+ try {
+ const firstbook = [];
+
+ if (!req.files?.file) {
+ return res
+ .status(422)
+ .json({ validation: { file: { msg: _sr["fa"].required.file } } });
+ }
+
+ if (req.files?.file.mimetype.split("/")[1] !== "pdf") {
+ return res
+ .status(422)
+ .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } });
+ }
+
+ const book = await Book.findById(req.body.bookId);
+ const bookId = book ? book._id : null;
+
+ const file = req.files.file;
+ const fileName =
+ req.body.title.replace(Date.now(), "-") + "_" + Date.now() + ".pdf";
+ const imageName =
+ req.body.title.replace(Date.now(), "-") + "_" + Date.now();
+
+ const { url: pdfUrl } = await uploadPdf(file.data, fileName);
+
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "book-"));
+ const tmpPdfPath = path.join(tmpDir, fileName);
+ await fs.writeFile(tmpPdfPath, file.data);
+
+ const result = await convertapi.convert(
+ "jpg",
+ { File: tmpPdfPath },
+ "pdf"
+ );
+
+ const convertedResponse = await axios.get(result.files[0].url, {
+ responseType: "arraybuffer",
+ });
+ const imagePath = await uploadBookSlide(
+ Buffer.from(convertedResponse.data),
+ imageName
+ );
+ firstbook.push(imagePath);
+
+ if (bookId) {
+ const update = await Book.findByIdAndUpdate(
+ { _id: bookId },
+ { $push: { slides: imagePath } },
+ { upsert: true }
+ );
+ return res
+ .status(201)
+ .json({ msg: "اسلاید کتاب اضافه شد", Data: update });
+ }
+
+ const newBook = new Book({
+ title: req.body.title,
+ uri: pdfUrl,
+ slides: firstbook,
+ });
+ await newBook.save();
+ return res.status(201).json({ msg: "کتاب ایجاد شد", Data: newBook });
+ } catch (error) {
+ console.error(error);
+ return res.status(500).json({
+ msg: "خطایی در پردازش فایل رخ داد",
+ error: error.message,
+ });
+ } finally {
+ if (tmpDir) {
+ await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
+ }
}
-
-
-
+ },
];
module.exports.GetAll = [
- async (req, res,next) => {
- try {
- const Books = await Book.find({});
- return res.status(200).json({ Books });
- } catch (error) {
- next(error)
- }
+ async (req, res, next) => {
+ try {
+ const Books = await Book.find({});
+ return res.status(200).json({ Books });
+ } catch (error) {
+ next(error);
}
+ },
];
module.exports.GetOne = [
- async (req, res,next) => {
- try {
- const id = req.params.id
- const book = await Book.findById(id);
- return res.status(200).json({ msg: book });
- } catch (error) {
- next(error)
- }
+ async (req, res, next) => {
+ try {
+ const id = req.params.id;
+ const book = await Book.findById(id);
+ return res.status(200).json({ msg: book });
+ } catch (error) {
+ next(error);
}
+ },
];
+
module.exports.DeleteOne = [
- async(req,res,next)=>{
- try {
- const book = await Book.deleteOne({_id:req.params.id})
- if(!book){
- return res.status(200).json({ msg: 'کتاب وجود ندارد ' });
- }
- return res.status(200).json({ msg: 'کتاب حذف شد' });
- } catch (error) {
- next(error)
- }
+ async (req, res, next) => {
+ try {
+ const book = await Book.findById(req.params.id);
+ if (!book) {
+ return res.status(200).json({ msg: "کتاب وجود ندارد " });
+ }
+
+ const uri = book.get("uri", null, { getters: false });
+ if (uri) {
+ await deletePdf(uri.split("/").pop());
+ }
+
+ const slides = book.get("slides", [], { getters: false }) || [];
+ for (const slide of slides) {
+ const imageName = slide.split("/").pop().replace(/\.jpeg$/i, "");
+ await deleteBookSlide(imageName);
+ }
+
+ await book.deleteOne();
+ return res.status(200).json({ msg: "کتاب حذف شد" });
+ } catch (error) {
+ next(error);
}
-]
+ },
+];
diff --git a/server/controllers/servicesController.js b/server/controllers/servicesController.js
index e99b9dd..c326c08 100644
--- a/server/controllers/servicesController.js
+++ b/server/controllers/servicesController.js
@@ -3,7 +3,6 @@ const {
body,
validationResult
} = require('express-validator');
-const sharp = require('sharp');
const {
_sr
} = require('../plugins/serverResponses');
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol,
generateRandomDigits
} = require('../plugins/controllersHelperFunctions');
-const fs = require('fs');
+const {
+ deleteSliderImage,
+ uploadProcessedImage,
+} = require('../plugins/sliderImageStorage');
////// variables
const profileWidth = 600;
@@ -113,15 +115,13 @@ module.exports.addSlider = [
}
const imageName = `slider_${Date.now()}_${createSlider._id}.${image.mimetype.split('/')[1]}`;
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/slider/${imageName}`);
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
createSlider.cover = imageName;
@@ -253,28 +253,15 @@ module.exports.updateSlider = [
}
const imageName = `slider_${Date.now()}_${targetSlider._id}.${image.mimetype.split('/')[1]}`;
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/slider/${imageName}`);
+ await deleteSliderImage(targetSlider, "cover");
- /**
- * if exist old pic delete it from file
- */
- if (targetSlider.cover) {
- var oldPic = `./static${targetSlider.cover}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
targetSlider.cover = imageName;
} catch (e) {
@@ -322,15 +309,7 @@ module.exports.deletOneSlider = [
return res500(res, `شما اجازه حذف این اسلایدر را ندارید`);
}
- if (targetSlider.cover) {
- var oldPic = `./static${targetSlider.cover}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (error) => {
- if (error) return res500(res, error?.message);
- });
- }
- }
+ await deleteSliderImage(targetSlider, "cover");
/** delete slider */
await targetSlider.deleteOne();
diff --git a/server/controllers/sliderController.js b/server/controllers/sliderController.js
index 4f5892d..d78eb6f 100644
--- a/server/controllers/sliderController.js
+++ b/server/controllers/sliderController.js
@@ -3,7 +3,6 @@ const {
body,
validationResult
} = require('express-validator');
-const sharp = require('sharp');
const {
_sr
} = require('../plugins/serverResponses');
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol,
generateRandomDigits
} = require('../plugins/controllersHelperFunctions');
-const fs = require('fs');
+const {
+ deleteSliderImage,
+ uploadProcessedImage,
+} = require('../plugins/sliderImageStorage');
////// variables
const profileWidth = 880;
@@ -113,15 +115,13 @@ module.exports.addSlider = [
}
const imageName = `slider_${Date.now()}_${createSlider._id}.${image.mimetype.split('/')[1]}`;
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/slider/${imageName}`);
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
createSlider.cover = imageName;
@@ -253,28 +253,15 @@ module.exports.updateSlider = [
}
const imageName = `slider_${Date.now()}_${targetSlider._id}.${image.mimetype.split('/')[1]}`;
try {
- const target = sharp(image.data)
- await target
- .resize(profileWidth, profileHeight, {
- fit: 'cover'
- })
- .jpeg({
- quality: profileQuality
- })
- .toFile(`./static/uploads/images/slider/${imageName}`);
+ await deleteSliderImage(targetSlider, "cover");
- /**
- * if exist old pic delete it from file
- */
- if (targetSlider.cover) {
- var oldPic = `./static${targetSlider.cover}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
targetSlider.cover = imageName;
} catch (e) {
@@ -322,15 +309,7 @@ module.exports.deletOneSlider = [
return res500(res, `شما اجازه حذف این اسلایدر را ندارید`);
}
- if (targetSlider.cover) {
- var oldPic = `./static${targetSlider.cover}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (error) => {
- if (error) return res500(res, error?.message);
- });
- }
- }
+ await deleteSliderImage(targetSlider, "cover");
/** delete slider */
await targetSlider.deleteOne();
diff --git a/server/controllers/userController.js b/server/controllers/userController.js
index d181d0b..5dda2a5 100644
--- a/server/controllers/userController.js
+++ b/server/controllers/userController.js
@@ -4,7 +4,6 @@ const { rateLimit } = require("express-rate-limit");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
-const sharp = require("sharp");
const { secretKey } = require("./../authentication");
const { _sr } = require("../plugins/serverResponses");
const {
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol,
generateRandomDigits,
} = require("../plugins/controllersHelperFunctions");
-const fs = require("fs");
+const {
+ deleteUserImage,
+ uploadProcessedImage,
+} = require("../plugins/userImageStorage");
const nodemailer = require("nodemailer");
const Category = require("../models/Category");
const Logger = require("../models/Logger");
@@ -566,15 +568,13 @@ module.exports.registerAdmin = [
image.mimetype.split("/")[1];
data.profilePic = imageName;
try {
- const target = sharp(image.data);
- await target
- .resize(profileWidth, profileHeight, {
- fit: "cover",
- })
- .jpeg({
- quality: profileQuality,
- })
- .toFile(`./static/uploads/images/users/${imageName}`);
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
} catch (e) {
return res500(res, e?.message);
}
@@ -713,18 +713,7 @@ module.exports.deleteOneUser = [
.json({ message: `شما نمیتوانید این کاربر را حذف کنید` });
}
- /**
- * if exist old pic delete it from file
- */
- if (user.profilePic) {
- var oldPic = `./static${user.profilePic}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await deleteUserImage(user, "profilePic");
/** delete user */
await user.deleteOne();
@@ -922,28 +911,15 @@ module.exports.updateUser = [
image.mimetype.split("/")[1];
try {
- const target = sharp(image.data);
- await target
- .resize(profileWidth, profileHeight, {
- fit: "cover",
- })
- .jpeg({
- quality: profileQuality,
- })
- .toFile(`./static/uploads/images/users/${imageName}`);
+ await deleteUserImage(user, "profilePic");
- /**
- * if exist old pic delete it from file
- */
- if (user.profilePic) {
- var oldPic = `./static${user.profilePic}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
} catch (e) {
return res500(res, e?.message);
}
@@ -1129,28 +1105,15 @@ module.exports.updateProfile = [
image.mimetype.split("/")[1];
try {
- const target = sharp(image.data);
- await target
- .resize(profileWidth, profileHeight, {
- fit: "cover",
- })
- .jpeg({
- quality: profileQuality,
- })
- .toFile(`./static/uploads/images/users/${imageName}`);
+ await deleteUserImage(user, "profilePic");
- /**
- * if exist old pic delete it from file
- */
- if (user.profilePic) {
- var oldPic = `./static${user.profilePic}`;
-
- if (fs.existsSync(oldPic)) {
- fs.unlink(oldPic, (err) => {
- if (err) return res500(res, err?.message);
- });
- }
- }
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ profileWidth,
+ profileHeight,
+ profileQuality
+ );
} catch (e) {
return res500(res, e?.message);
}
diff --git a/server/controllers/yearBannerController.js b/server/controllers/yearBannerController.js
index 71aa1e5..503b3a7 100644
--- a/server/controllers/yearBannerController.js
+++ b/server/controllers/yearBannerController.js
@@ -2,8 +2,7 @@ const YearBanner = require('../models/YearBanner');
const {body, validationResult, checkSchema} = require('express-validator');
const {_sr} = require('../plugins/serverResponses');
const {res404, res500} = require('../plugins/controllersHelperFunctions');
-const fs = require('fs');
-const sharp = require('sharp');
+const {uploadProcessedImage} = require('../plugins/yearBannerImageStorage');
////// variables
const yearBannerWidth = 1920
@@ -51,12 +50,13 @@ module.exports.updateYearBanner = [
imageName = `mainPage_cover.jpg`;
- const target = sharp(image.data)
- await target
- .resize(yearBannerWidth, yearBannerHeight, {fit: 'cover'})
- .toFormat('jpg')
- .jpeg({quality: yearBannerQuality})
- .toFile(`./static/uploads/images/mainCover/${imageName}`);
+ await uploadProcessedImage(
+ image.data,
+ imageName,
+ yearBannerWidth,
+ yearBannerHeight,
+ yearBannerQuality
+ );
}
if (yearBanner) {
diff --git a/server/models/Book.js b/server/models/Book.js
index d606581..84facae 100644
--- a/server/models/Book.js
+++ b/server/models/Book.js
@@ -1,16 +1,31 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
+const s3Storage = require('../plugins/s3Storage');
-
-function PDF(pdf) {
- if (pdf) return '/uploads/files/ReadOnly-PDF' + pdf
+function pdfUri(uri) {
+ if (!uri) return null;
+ if (uri.startsWith('http://') || uri.startsWith('https://')) return uri;
+ const fileName = uri.startsWith('/') ? uri.split('/').pop() : uri;
+ return s3Storage.getPublicUrl(`uploads/pdf/${fileName}`);
}
+
+function slideUrl(slide) {
+ if (!slide) return null;
+ if (slide.startsWith('http://') || slide.startsWith('https://')) return slide;
+ const imageName = slide.startsWith('/') ? slide.split('/').pop() : slide;
+ const key = imageName.endsWith('.jpeg')
+ ? `uploads/books/${imageName}`
+ : `uploads/books/${imageName}.jpeg`;
+ return s3Storage.getPublicUrl(key);
+}
+
const BookSchema = mongoose.Schema({
title: String,
- uri: String,
- slides:{
- default:[],
- type:Array
+ uri: { type: String, get: pdfUri, default: null },
+ slides: {
+ default: [],
+ type: [String],
+ get: (slides) => (slides || []).map(slideUrl),
}
});
diff --git a/server/models/Category.js b/server/models/Category.js
index 7190473..0f6f4e6 100644
--- a/server/models/Category.js
+++ b/server/models/Category.js
@@ -1,4 +1,5 @@
const mongoose = require("mongoose");
+const s3Storage = require("../plugins/s3Storage");
function autoPopulateSubs(next) {
this.populate({
@@ -14,12 +15,21 @@ function autoPopulateSubs(next) {
}
function file(file) {
- return "/uploads/files/terms/" + file;
+ if (!file) return null;
+ if (file.startsWith("http://") || file.startsWith("https://")) return file;
+ return s3Storage.getPublicUrl(`uploads/files/terms/${file}`);
}
function image(img) {
- if (img) return "/uploads/images/category/" + img;
- else return "/img/no-cover.jpg";
+ if (!img) return "/img/no-cover.jpg";
+ if (img.startsWith("http://") || img.startsWith("https://")) return img;
+ return s3Storage.getPublicUrl(`uploads/images/category/${img}`);
+}
+
+function shafafiatIcon(icon) {
+ if (!icon) return null;
+ if (icon.startsWith("http://") || icon.startsWith("https://")) return icon;
+ return s3Storage.getPublicUrl(`uploads/images/category/shafafiat/${icon}`);
}
const CategoryTermsFile = mongoose.Schema({
@@ -71,7 +81,7 @@ const CategorySchema = mongoose.Schema({
managerName: String,
managerDescription: String,
/** if category was shafafiat this icon is for that */
- icon: String,
+ icon: { type: String, get: shafafiatIcon, default: null },
});
CategorySchema.index({ parent: 1, childs: 1 });
diff --git a/server/models/Content.js b/server/models/Content.js
index 42f45ec..a3f4445 100644
--- a/server/models/Content.js
+++ b/server/models/Content.js
@@ -1,7 +1,10 @@
const mongoose = require('mongoose');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/content/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/content/${img}`);
}
diff --git a/server/models/Election.js b/server/models/Election.js
index e626751..c0f0f83 100644
--- a/server/models/Election.js
+++ b/server/models/Election.js
@@ -1,8 +1,11 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/election/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/election/${img}`);
}
const CandidatesSchema = mongoose.Schema({
diff --git a/server/models/Links.js b/server/models/Links.js
index d099d6f..1f4ddc0 100644
--- a/server/models/Links.js
+++ b/server/models/Links.js
@@ -1,7 +1,10 @@
const mongoose = require('mongoose');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/links/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/links/${img}`);
}
const LinksSchema = mongoose.Schema({
diff --git a/server/models/Occasion.js b/server/models/Occasion.js
index 6c300ec..c5e7a82 100644
--- a/server/models/Occasion.js
+++ b/server/models/Occasion.js
@@ -1,8 +1,11 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/occasion/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/occasion/${img}`);
}
const OccasionSchema = mongoose.Schema({
diff --git a/server/models/Slider.js b/server/models/Slider.js
index fd9f017..05ec6ba 100644
--- a/server/models/Slider.js
+++ b/server/models/Slider.js
@@ -1,8 +1,11 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/slider/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/slider/${img}`);
}
const SliderSchema = mongoose.Schema({
diff --git a/server/models/User.js b/server/models/User.js
index 7d35028..cc3574a 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -1,8 +1,11 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/users/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/users/${img}`);
}
const AdminSchema = mongoose.Schema({
diff --git a/server/models/YearBanner.js b/server/models/YearBanner.js
index 43fdbda..80e1919 100644
--- a/server/models/YearBanner.js
+++ b/server/models/YearBanner.js
@@ -1,7 +1,10 @@
const mongoose = require('mongoose');
+const s3Storage = require('../plugins/s3Storage');
function image(img) {
- if (img) return '/uploads/images/mainCover/' + img
+ if (!img) return null;
+ if (img.startsWith('http://') || img.startsWith('https://')) return img;
+ return s3Storage.getPublicUrl(`uploads/images/mainCover/${img}`);
}
const YearBannerSchema = mongoose.Schema({
diff --git a/server/plugins/backupStorage.js b/server/plugins/backupStorage.js
new file mode 100644
index 0000000..e3a7fb6
--- /dev/null
+++ b/server/plugins/backupStorage.js
@@ -0,0 +1,33 @@
+const s3Storage = require("./s3Storage");
+
+const BACKUP_PREFIX = "backup";
+const UPLOADS_PREFIX = "uploads";
+
+const appendS3UploadsToArchive = async (archive) => {
+ const keys = await s3Storage.listObjects(UPLOADS_PREFIX);
+
+ for (const key of keys) {
+ const buffer = await s3Storage.getObjectBuffer(key);
+ archive.append(buffer, { name: key });
+ }
+};
+
+const uploadBackupArchive = async (buffer, fileName) => {
+ await s3Storage.uploadBuffer(
+ `${BACKUP_PREFIX}/${fileName}`,
+ buffer,
+ "application/zip"
+ );
+};
+
+const deleteOldBackups = async () => {
+ await s3Storage.deleteObjectsByPrefix(BACKUP_PREFIX);
+};
+
+module.exports = {
+ BACKUP_PREFIX,
+ UPLOADS_PREFIX,
+ appendS3UploadsToArchive,
+ uploadBackupArchive,
+ deleteOldBackups,
+};
diff --git a/server/plugins/bookImageStorage.js b/server/plugins/bookImageStorage.js
new file mode 100644
index 0000000..e354bf0
--- /dev/null
+++ b/server/plugins/bookImageStorage.js
@@ -0,0 +1,48 @@
+const s3Storage = require("./s3Storage");
+
+const PDF_PREFIX = "uploads/pdf";
+const BOOK_SLIDE_PREFIX = "uploads/books";
+
+const uploadPdf = async (fileData, fileName) => {
+ const key = `${PDF_PREFIX}/${fileName}`;
+ await s3Storage.uploadBuffer(key, fileData, "application/pdf");
+ return {
+ fileName,
+ url: s3Storage.getPublicUrl(key),
+ };
+};
+
+const uploadBookSlide = async (buffer, imageName) => {
+ const key = `${BOOK_SLIDE_PREFIX}/${imageName}.jpeg`;
+ await s3Storage.uploadBuffer(key, buffer, "image/jpeg");
+ return s3Storage.getPublicUrl(key);
+};
+
+const deletePdf = async (fileName) => {
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${PDF_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log("Failed to delete PDF from S3:", error?.message);
+ }
+};
+
+const deleteBookSlide = async (imageName) => {
+ if (!imageName) return;
+
+ try {
+ await s3Storage.deleteObject(`${BOOK_SLIDE_PREFIX}/${imageName}.jpeg`);
+ } catch (error) {
+ console.log("Failed to delete book slide from S3:", error?.message);
+ }
+};
+
+module.exports = {
+ PDF_PREFIX,
+ BOOK_SLIDE_PREFIX,
+ uploadPdf,
+ uploadBookSlide,
+ deletePdf,
+ deleteBookSlide,
+};
diff --git a/server/plugins/categoryImageStorage.js b/server/plugins/categoryImageStorage.js
new file mode 100644
index 0000000..f45533c
--- /dev/null
+++ b/server/plugins/categoryImageStorage.js
@@ -0,0 +1,94 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const CATEGORY_IMAGE_PREFIX = "uploads/images/category";
+const CATEGORY_SHAFAFIAT_PREFIX = "uploads/images/category/shafafiat";
+const TERMS_FILE_PREFIX = "uploads/files/terms";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteCategoryImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${CATEGORY_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const deleteShafafiatIcon = async (fileName) => {
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${CATEGORY_SHAFAFIAT_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log("Failed to delete shafafiat icon from S3:", error?.message);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${CATEGORY_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+const uploadShafafiatIcon = async (fileData, fileName, contentType) => {
+ await s3Storage.uploadBuffer(
+ `${CATEGORY_SHAFAFIAT_PREFIX}/${fileName}`,
+ fileData,
+ contentType
+ );
+
+ return fileName;
+};
+
+const uploadTermsFile = async (fileData, fileName) => {
+ await s3Storage.uploadBuffer(
+ `${TERMS_FILE_PREFIX}/${fileName}`,
+ fileData,
+ "application/pdf"
+ );
+
+ return fileName;
+};
+
+const deleteTermsFile = async (fileName) => {
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${TERMS_FILE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log("Failed to delete terms file from S3:", error?.message);
+ }
+};
+
+module.exports = {
+ CATEGORY_IMAGE_PREFIX,
+ CATEGORY_SHAFAFIAT_PREFIX,
+ TERMS_FILE_PREFIX,
+ getStoredImageName,
+ deleteCategoryImage,
+ deleteShafafiatIcon,
+ uploadProcessedImage,
+ uploadShafafiatIcon,
+ uploadTermsFile,
+ deleteTermsFile,
+};
diff --git a/server/plugins/contentImageStorage.js b/server/plugins/contentImageStorage.js
new file mode 100644
index 0000000..5732bda
--- /dev/null
+++ b/server/plugins/contentImageStorage.js
@@ -0,0 +1,52 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const CONTENT_IMAGE_PREFIX = "uploads/images/content";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteContentImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${CONTENT_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const deleteContentImages = async (doc) => {
+ await deleteContentImage(doc, "cover");
+ await deleteContentImage(doc, "thumb");
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${CONTENT_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ CONTENT_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteContentImage,
+ deleteContentImages,
+ uploadProcessedImage,
+};
diff --git a/server/plugins/electionImageStorage.js b/server/plugins/electionImageStorage.js
new file mode 100644
index 0000000..06129cd
--- /dev/null
+++ b/server/plugins/electionImageStorage.js
@@ -0,0 +1,58 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const ELECTION_IMAGE_PREFIX = "uploads/images/election";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteElectionImage = async (fileName) => {
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${ELECTION_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete election image from S3:`, error?.message);
+ }
+};
+
+const deleteCandidateImage = async (candidate) => {
+ const fileName = getStoredImageName(candidate, "profilePic");
+ await deleteElectionImage(fileName);
+};
+
+const deleteElectionCandidateImages = async (election) => {
+ for (const candidate of election.candidates) {
+ await deleteCandidateImage(candidate);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${ELECTION_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ ELECTION_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteElectionImage,
+ deleteCandidateImage,
+ deleteElectionCandidateImages,
+ uploadProcessedImage,
+};
diff --git a/server/plugins/linksImageStorage.js b/server/plugins/linksImageStorage.js
new file mode 100644
index 0000000..c6a753f
--- /dev/null
+++ b/server/plugins/linksImageStorage.js
@@ -0,0 +1,46 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const LINKS_IMAGE_PREFIX = "uploads/images/links";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteLinksImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${LINKS_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${LINKS_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ LINKS_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteLinksImage,
+ uploadProcessedImage,
+};
diff --git a/server/plugins/occasionImageStorage.js b/server/plugins/occasionImageStorage.js
new file mode 100644
index 0000000..6ee91a8
--- /dev/null
+++ b/server/plugins/occasionImageStorage.js
@@ -0,0 +1,46 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const OCCASION_IMAGE_PREFIX = "uploads/images/occasion";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteOccasionImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${OCCASION_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${OCCASION_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ OCCASION_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteOccasionImage,
+ uploadProcessedImage,
+};
diff --git a/server/plugins/s3Storage.js b/server/plugins/s3Storage.js
index f16c02e..7a5c949 100644
--- a/server/plugins/s3Storage.js
+++ b/server/plugins/s3Storage.js
@@ -2,6 +2,8 @@ const {
S3Client,
PutObjectCommand,
DeleteObjectCommand,
+ ListObjectsV2Command,
+ GetObjectCommand,
} = require("@aws-sdk/client-s3");
const config = {
@@ -85,9 +87,54 @@ const deleteObject = async (key) => {
);
};
+const listObjects = async (prefix = "") => {
+ const keys = [];
+ let continuationToken;
+
+ do {
+ const response = await getClient().send(
+ new ListObjectsV2Command({
+ Bucket: config.bucket,
+ Prefix: normalizeKey(prefix),
+ ContinuationToken: continuationToken,
+ })
+ );
+
+ for (const item of response.Contents || []) {
+ if (item.Key) keys.push(item.Key);
+ }
+
+ continuationToken = response.IsTruncated
+ ? response.NextContinuationToken
+ : undefined;
+ } while (continuationToken);
+
+ return keys;
+};
+
+const getObjectBuffer = async (key) => {
+ const response = await getClient().send(
+ new GetObjectCommand({
+ Bucket: config.bucket,
+ Key: normalizeKey(key),
+ })
+ );
+
+ return Buffer.from(await response.Body.transformToByteArray());
+};
+
+const deleteObjectsByPrefix = async (prefix) => {
+ const keys = await listObjects(prefix);
+
+ await Promise.all(keys.map((key) => deleteObject(key)));
+};
+
module.exports = {
isConfigured,
getPublicUrl,
uploadBuffer,
deleteObject,
+ listObjects,
+ getObjectBuffer,
+ deleteObjectsByPrefix,
};
diff --git a/server/plugins/sliderImageStorage.js b/server/plugins/sliderImageStorage.js
new file mode 100644
index 0000000..8387c3e
--- /dev/null
+++ b/server/plugins/sliderImageStorage.js
@@ -0,0 +1,46 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const SLIDER_IMAGE_PREFIX = "uploads/images/slider";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteSliderImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${SLIDER_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${SLIDER_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ SLIDER_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteSliderImage,
+ uploadProcessedImage,
+};
diff --git a/server/plugins/userImageStorage.js b/server/plugins/userImageStorage.js
new file mode 100644
index 0000000..4a6bddb
--- /dev/null
+++ b/server/plugins/userImageStorage.js
@@ -0,0 +1,46 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const USER_IMAGE_PREFIX = "uploads/images/users";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteUserImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${USER_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${USER_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ USER_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteUserImage,
+ uploadProcessedImage,
+};
diff --git a/server/plugins/yearBannerImageStorage.js b/server/plugins/yearBannerImageStorage.js
new file mode 100644
index 0000000..62c02db
--- /dev/null
+++ b/server/plugins/yearBannerImageStorage.js
@@ -0,0 +1,46 @@
+const sharp = require("sharp");
+const s3Storage = require("./s3Storage");
+
+const YEAR_BANNER_IMAGE_PREFIX = "uploads/images/mainCover";
+
+const getStoredImageName = (doc, field) =>
+ doc.get(field, null, { getters: false });
+
+const deleteYearBannerImage = async (doc, field) => {
+ const fileName = getStoredImageName(doc, field);
+ if (!fileName) return;
+
+ try {
+ await s3Storage.deleteObject(`${YEAR_BANNER_IMAGE_PREFIX}/${fileName}`);
+ } catch (error) {
+ console.log(`Failed to delete ${field} from S3:`, error?.message);
+ }
+};
+
+const uploadProcessedImage = async (
+ imageData,
+ imageName,
+ width,
+ height,
+ quality
+) => {
+ const buffer = await sharp(imageData)
+ .resize(width, height, { fit: "cover" })
+ .jpeg({ quality })
+ .toBuffer();
+
+ await s3Storage.uploadBuffer(
+ `${YEAR_BANNER_IMAGE_PREFIX}/${imageName}`,
+ buffer,
+ "image/jpeg"
+ );
+
+ return imageName;
+};
+
+module.exports = {
+ YEAR_BANNER_IMAGE_PREFIX,
+ getStoredImageName,
+ deleteYearBannerImage,
+ uploadProcessedImage,
+};