Compare commits

14 Commits

Author SHA1 Message Date
morteza 94c79aa98d db string 2026-06-20 00:18:29 +03:30
morteza 01c68fba43 package 2026-06-20 00:02:53 +03:30
morteza e0527e8ca6 remove postinstall 2026-06-19 23:54:40 +03:30
morteza 1bb4c7efcc docker 2026-06-19 23:48:35 +03:30
morteza 76f23a73e2 docker 2026-06-19 23:43:23 +03:30
morteza a24f4ae766 docker update 2026-06-19 23:31:03 +03:30
morteza 096e653e64 update db string 2026-06-19 23:28:45 +03:30
morteza d6215b970f docker 2026-06-19 23:25:24 +03:30
morteza 4c36880862 docker update 2026-06-19 23:20:19 +03:30
morteza dfb77cd8d2 use self hosted packages 2026-06-19 19:02:51 +03:30
morteza e95883eb6a S3 for other controolers 2026-06-19 15:47:02 +03:30
morteza c73027540a S3 gallery 2026-06-19 12:35:49 +03:30
morteza 85c8107799 S3 : news controller 2026-06-19 12:26:40 +03:30
morteza 99a9f9ace5 S3 : add news 2026-06-19 12:11:18 +03:30
2918 changed files with 42987 additions and 1915 deletions
+7
View File
@@ -0,0 +1,7 @@
# ParsPack S3 Storage
BUCKET_NAME=your_bucket_name
BUCKET_REGION=us-west-2
PARSPACK_S3_ENDPOINT=https://your_bucket_name.parspack.net
BUCKET_PUBLIC_URL=https://your_bucket_name.parspack.net
BUCKET_ACCESS_KEY=your_access_key
BUCKET_SECRET_KEY=your_secret_key
+4 -10
View File
@@ -43,25 +43,19 @@ export default {
type: "text/css", type: "text/css",
href: "/js/flipBook/font-awesome.css", href: "/js/flipBook/font-awesome.css",
}, },
{
rel: "stylesheet",
href: "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js",
},
], ],
script: [ script: [
{ src: "https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.js" }, { src: "/js/vendor/jquery-1.8.3.js" },
{ { src: "/js/vendor/jquery-ui-1.9.2.min.js" },
src: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js",
},
{ src: "/js/flipBook/flipbook.min.js" }, { src: "/js/flipBook/flipbook.min.js" },
{ src: "/js/lightBox/simple-lightbox.min.js" }, { src: "/js/lightBox/simple-lightbox.min.js" },
{ {
src: "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js", src: "/js/vendor/jquery-3.6.0.min.js",
defer: true, defer: true,
}, },
{ {
src: "https://cdnjs.cloudflare.com/ajax/libs/jquery-flipbook/1.0.5/jquery.flipbook.min.js", src: "/js/vendor/jquery.flipbook.min.js",
defer: true, defer: true,
}, },
], ],
+1154 -733
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -11,6 +11,7 @@
"generate": "nuxt generate" "generate": "nuxt generate"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1072.0",
"@coreui/coreui": "^3.2.2", "@coreui/coreui": "^3.2.2",
"@coreui/icons": "^2.0.0-beta.5", "@coreui/icons": "^2.0.0-beta.5",
"@coreui/utils": "^1.3.1", "@coreui/utils": "^1.3.1",
@@ -25,10 +26,12 @@
"archiver": "^5.3.0", "archiver": "^5.3.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"ckeditor4": "^4.17.2",
"ckeditor4-vue": "^1.3.0", "ckeditor4-vue": "^1.3.0",
"convertapi": "^1.14.0", "convertapi": "^1.14.0",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.4.2",
"element-ui": "^2.15.1", "element-ui": "^2.15.1",
"express": "^4.17.1", "express": "^4.17.1",
"express-fileupload": "^1.2.1", "express-fileupload": "^1.2.1",
@@ -49,7 +52,7 @@
"nuxt": "^2.15.3", "nuxt": "^2.15.3",
"nuxt-video-player": "^1.0.17", "nuxt-video-player": "^1.0.17",
"request-ip": "^2.1.3", "request-ip": "^2.1.3",
"sharp": "^0.28.1", "sharp": "^0.33.5",
"slick-carousel": "^1.8.1", "slick-carousel": "^1.8.1",
"svg-captcha": "^1.4.0", "svg-captcha": "^1.4.0",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
@@ -66,5 +69,8 @@
"sass": "^1.77.3", "sass": "^1.77.3",
"sass-loader": "^10.1.0", "sass-loader": "^10.1.0",
"webpack": "^4.46.0" "webpack": "^4.46.0"
},
"overrides": {
"sharp": "^0.33.5"
} }
} }
+1 -1
View File
@@ -36,7 +36,7 @@
v-for="(item, i) in items" v-for="(item, i) in items"
:key="i" :key="i"
class="item"> class="item">
<img :src="`/uploads/images/category/shafafiat/${item.icon}`" alt="شفافیت" /> <img :src="item.icon" alt="شفافیت" />
<div class="parent"> <div class="parent">
<div class="right"></div> <div class="right"></div>
<div class="left"></div> <div class="left"></div>
+15 -1
View File
@@ -1,4 +1,18 @@
import Vue from 'vue' import Vue from 'vue'
import CKEditor from 'ckeditor4-vue' import CKEditor from 'ckeditor4-vue'
Vue.use(CKEditor) const editorUrl = '/js/ckeditor/ckeditor.js'
window.CKEDITOR_BASEPATH = '/js/ckeditor/'
// editorUrl is a component prop, not a Vue.use() option — the default is the CDN.
Vue.component('ckeditor', {
...CKEditor.component,
props: {
...CKEditor.component.props,
editorUrl: {
type: String,
default: editorUrl,
},
},
})
+68 -120
View File
@@ -8,13 +8,19 @@ const {
checkValidations, checkValidations,
} = require("../plugins/controllersHelperFunctions"); } = require("../plugins/controllersHelperFunctions");
const News = require("../models/News"); const News = require("../models/News");
const sharp = require("sharp");
const fs = require("fs");
const { extname } = require("path"); const { extname } = require("path");
const {
deleteCategoryImage,
deleteShafafiatIcon,
uploadProcessedImage,
uploadShafafiatIcon,
uploadTermsFile,
deleteTermsFile,
getStoredImageName,
} = require("../plugins/categoryImageStorage");
// variables // variables
const titleFilterRegex = new RegExp("/", "g"); const titleFilterRegex = new RegExp("/", "g");
const noCover = "/img/no-cover.jpg";
const coverNewsWidth = 400; const coverNewsWidth = 400;
const coverNewsHeight = 400; const coverNewsHeight = 400;
const coverQuality = 100; const coverQuality = 100;
@@ -411,34 +417,13 @@ module.exports.addCategory = [
image.mimetype.split("/")[1] image.mimetype.split("/")[1]
}`; }`;
try { try {
const target = sharp(image.data); await uploadProcessedImage(
await target image.data,
.resize(coverNewsWidth, coverNewsHeight, { fit: "cover" }) imageName,
.jpeg({ quality: coverQuality }) coverNewsWidth,
.toFile(`./static/uploads/images/category/${imageName}`); coverNewsHeight,
coverQuality
/** );
* 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.managerCover = imageName; create.managerCover = imageName;
await create.save(); await create.save();
@@ -461,33 +446,12 @@ module.exports.addCategory = [
const ext = extname(image.name); const ext = extname(image.name);
const imageName = `shafafiat_icon_${Date.now()}${ext}`; const imageName = `shafafiat_icon_${Date.now()}${ext}`;
try { try {
await image.mv( await uploadShafafiatIcon(
`${process.cwd()}/static/uploads/images/category/shafafiat/${imageName}` 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; create.icon = imageName;
await create.save(); await create.save();
} catch (e) { } catch (e) {
@@ -812,24 +776,15 @@ module.exports.updateCategory = [
image.mimetype.split("/")[1] image.mimetype.split("/")[1]
}`; }`;
try { try {
const target = sharp(image.data); await deleteCategoryImage(targetCategory, "managerCover");
await target
.resize(coverNewsWidth, coverNewsHeight, { fit: "cover" })
.jpeg({ quality: coverQuality })
.toFile(`./static/uploads/images/category/${imageName}`);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (targetCategory.managerCover !== noCover) { coverNewsWidth,
let oldPic = `./static${targetCategory.managerCover}`; coverNewsHeight,
coverQuality
if (await fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
targetCategory.managerCover = imageName; targetCategory.managerCover = imageName;
} catch (e) { } catch (e) {
@@ -839,7 +794,6 @@ module.exports.updateCategory = [
if (req.files?.icon) { if (req.files?.icon) {
const image = req.files.icon; const image = req.files.icon;
console.log(image);
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) { if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) {
return res.status(422).json({ return res.status(422).json({
validation: { validation: {
@@ -852,30 +806,17 @@ module.exports.updateCategory = [
const ext = extname(image.name); const ext = extname(image.name);
const imageName = `shafafiat_icon_${Date.now()}${ext}`; const imageName = `shafafiat_icon_${Date.now()}${ext}`;
try { try {
await image.mv( await deleteShafafiatIcon(
`${process.cwd()}/static/uploads/images/category/shafafiat/${imageName}` 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}`);
/** await uploadShafafiatIcon(
* if exist old pic delete it from file image.data,
*/ imageName,
if (targetCategory.icon !== noCover) { image.mimetype
let oldPic = `./static${targetCategory.icon}`; );
if (await fs.existsSync(oldPic)) {
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
targetCategory.icon = imageName; targetCategory.icon = imageName;
console.log(targetCategory);
} catch (e) { } catch (e) {
return res500(res, e?.message); return res500(res, e?.message);
} }
@@ -1223,7 +1164,8 @@ module.exports.getParent = [
module.exports.addTermsFileToCategory = [ module.exports.addTermsFileToCategory = [
[body("termsTitle").notEmpty().withMessage(_sr["fa"].required.title)], [body("termsTitle").notEmpty().withMessage(_sr["fa"].required.title)],
checkValidations(validationResult), checkValidations(validationResult),
(req, res) => { async (req, res) => {
try {
if (!req.files?.file) if (!req.files?.file)
return res return res
.status(422) .status(422)
@@ -1233,42 +1175,48 @@ module.exports.addTermsFileToCategory = [
.status(422) .status(422)
.json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } }); .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } });
const data = { title: req.body.termsTitle }; const category = await Category.findById(req.params.categoryId);
const file = req.files.file; if (!category) return res404(res, "category not found");
const fileName = const fileName =
req.body.termsTitle.replace(titleFilterRegex, "-") + req.body.termsTitle.replace(titleFilterRegex, "-") +
"_" + "_" +
Date.now() + Date.now() +
".pdf"; ".pdf";
file.mv(`./static/uploads/files/terms/${fileName}`);
data.file = fileName;
Category.findById(req.params.categoryId, (err, category) => { await uploadTermsFile(req.files.file.data, fileName);
if (err || !category) return res404(res, err);
category.terms.push(data); category.terms.push({
category.save((err) => { title: req.body.termsTitle,
if (err) return res500(res, err); file: fileName,
else return res.json({ message: _sr["fa"].response.success_save });
});
}); });
await category.save();
return res.json({ message: _sr["fa"].response.success_save });
} catch (err) {
return res500(res, err);
}
}, },
]; ];
module.exports.removeTermsFileFromCategory = [ module.exports.removeTermsFileFromCategory = [
(req, res) => { async (req, res) => {
Category.findOne({ "terms._id": req.params.fileId }, (err, category) => { try {
if (err || !category) return res404(res, err); const category = await Category.findOne({ "terms._id": req.params.fileId });
const file = category.terms.id(req.params.fileId); if (!category) return res404(res, "not found");
if (!file) return res404(res, "not found");
file.remove(); const termsFile = category.terms.id(req.params.fileId);
fs.unlink(`./static/${file.file}`, (err) => { if (!termsFile) return res404(res, "not found");
if (err) console.log(err);
}); const fileName = getStoredImageName(termsFile, "file");
category.save((err) => { termsFile.remove();
if (err) return res500(res, err); await deleteTermsFile(fileName);
else return res.json({ message: _sr["fa"].response.success_remove }); await category.save();
});
}); return res.json({ message: _sr["fa"].response.success_remove });
} catch (err) {
return res500(res, err);
}
}, },
]; ];
+34 -55
View File
@@ -1,9 +1,11 @@
const {body, validationResult} = require('express-validator'); const {body, validationResult} = require('express-validator');
const sharp = require('sharp');
const {_sr} = require('../plugins/serverResponses'); const {_sr} = require('../plugins/serverResponses');
const {res404, res500, hasWhiteSpaces, isLatinCharactersWithSymbol, generateRandomDigits} = require('../plugins/controllersHelperFunctions'); const {res404, res500, hasWhiteSpaces, isLatinCharactersWithSymbol, generateRandomDigits} = require('../plugins/controllersHelperFunctions');
const Content = require('../models/Content'); const Content = require('../models/Content');
const fs = require('fs'); const {
deleteContentImages,
uploadProcessedImage,
} = require('../plugins/contentImageStorage');
////// variables ////// variables
@@ -85,26 +87,23 @@ module.exports.addContent = [
const imageName = `manager_${Date.now()}_${create._id}.${image.mimetype.split('/')[1]}`; const imageName = `manager_${Date.now()}_${create._id}.${image.mimetype.split('/')[1]}`;
const thumbName = 'thumb_' + imageName const thumbName = 'thumb_' + imageName
try { try {
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(profileWidth, profileHeight, { imageName,
fit: 'cover' profileWidth,
}) profileHeight,
.jpeg({ profileQuality
quality: profileQuality );
}) await uploadProcessedImage(
.toFile(`./static/uploads/images/content/${imageName}`) image.data,
await target thumbName,
.resize(profileThumbWidth, profileThumbHeight, { profileThumbWidth,
fit: 'cover' profileThumbHeight,
}) profileThumbQuality
.jpeg({ );
quality: profileThumbQuality
})
.toFile(`./static/uploads/images/content/${thumbName}`)
create.cover = imageName; create.cover = imageName;
create.thumb = imageName; create.thumb = thumbName;
await create.save(); await create.save();
} catch (e) { } catch (e) {
@@ -180,42 +179,22 @@ module.exports.updateContent = [
const imageName = `manager_${Date.now()}_${targetContent._id}.${image.mimetype.split('/')[1]}`; const imageName = `manager_${Date.now()}_${targetContent._id}.${image.mimetype.split('/')[1]}`;
const thumbName = 'thumb_' + imageName const thumbName = 'thumb_' + imageName
try { try {
const target = sharp(image.data) await deleteContentImages(targetContent);
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(
* if exist old pic delete it from file image.data,
*/ imageName,
if (targetContent.cover) { profileWidth,
const oldPic = `./static${targetContent.cover}` profileHeight,
const oldThumb = `./static${targetContent.thumb}` profileQuality
);
if (fs.existsSync(oldPic)) { await uploadProcessedImage(
fs.unlink(oldPic, (err) => { image.data,
if (err) return res500(res, err?.message); thumbName,
}); profileThumbWidth,
} profileThumbHeight,
if (fs.existsSync(oldThumb)) { profileThumbQuality
fs.unlink(oldThumb, (err) => { );
if (err) return res500(res, err?.message);
});
}
}
targetContent.cover = imageName; targetContent.cover = imageName;
targetContent.thumb = thumbName; targetContent.thumb = thumbName;
+63 -63
View File
@@ -1,9 +1,15 @@
const fs = require('fs') const fs = require('fs')
const {PassThrough} = require('stream')
const archiver = require('archiver') const archiver = require('archiver')
const moment = require('moment') const moment = require('moment')
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken')
const {secretKey} = require('../authentication') const {secretKey} = require('../authentication')
const {_sr} = require('../plugins/serverResponses') const {_sr} = require('../plugins/serverResponses')
const {
appendS3UploadsToArchive,
uploadBackupArchive,
deleteOldBackups,
} = require('../plugins/backupStorage')
// models // models
const Category = require('../models/Category') const Category = require('../models/Category')
const Comment = require('../models/Comment') const Comment = require('../models/Comment')
@@ -21,6 +27,8 @@ const Slider = require('../models/Slider')
const User = require('../models/User') const User = require('../models/User')
const YearBanner = require('../models/YearBanner') const YearBanner = require('../models/YearBanner')
const hostFullError = '<h1 style="margin-top: 150px;text-align: center;direction: rtl;color: red;">فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.</h1>'
module.exports.createBackUp = [ module.exports.createBackUp = [
// check user token and permission to backup // check user token and permission to backup
async (req, res, next) => { 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) 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') const dateStr = moment(Date.now()).format('YYYY-MM-DD')
const backupName = `backup_${dateStr}.zip`
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 archive = archiver('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('<h1 style="margin-top: 150px;text-align: center;direction: rtl;color: red;">فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.</h1>')
})
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', function (err) { archive.on('warning', function (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
// log warning
console.log('On archiver warning -- ENOENT error code', err) console.log('On archiver warning -- ENOENT error code', err)
} else { } else {
// throw error
console.log('On archiver warning -- other error codes ', err) console.log('On archiver warning -- other error codes ', err)
} }
return res.status(500).send('<h1 style="margin-top: 150px;text-align: center;direction: rtl;color: red;">فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.</h1>') return res.status(500).send(hostFullError)
}) })
// good practice to catch this error explicitly
archive.on('error', function (err) { archive.on('error', function (err) {
// throw err
console.log(err) console.log(err)
return res.status(500).json({message: 'مشکلی پیش آمده'}) return res.status(500).json({message: 'مشکلی پیش آمده'})
}) })
// pipe archive data to the file archive.pipe(output)
await archive.pipe(output)
// append files from a sub-directory and naming it `new-subdir` within the archive const databaseExports = [
await archive.directory(`./static/uploads/`, 'uploads') ['categories', categories],
await archive.directory(`./backup/database/`, 'database') ['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 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) { } catch (e) {
return res.status(500).send('<h1 style="margin-top: 150px;text-align: center;direction: rtl;color: red;">فضای هاست پر میباشد.لطفا نسبت به پاک کردن بکاپ های قبلی یا افزایش فضای هاست اقدام فرمایید.</h1>') return res.status(500).send(hostFullError)
} }
} }
] ]
@@ -173,12 +167,18 @@ module.exports.createBackUp = [
module.exports.removeOldBackups = [ module.exports.removeOldBackups = [
async (req, res) => { async (req, res) => {
try { try {
const files = await fs.readdirSync('./backup') await deleteOldBackups()
for await (const file of files) {
const isDirectory = await fs.statSync(`./backup/${file}`).isDirectory() if (fs.existsSync('./backup')) {
if (isDirectory) await fs.rmdirSync(`./backup/${file}`, {recursive: true}) const files = fs.readdirSync('./backup')
else await fs.unlinkSync(`./backup/${file}`) 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}) return res.json({message: _sr['fa'].response.success_remove})
} catch (e) { } catch (e) {
return res.status(500).json({message: e}) return res.status(500).json({message: e})
+22 -53
View File
@@ -10,8 +10,11 @@ const {
hasWhiteSpaces, hasWhiteSpaces,
isLatinCharactersWithSymbol isLatinCharactersWithSymbol
} = require('../plugins/controllersHelperFunctions'); } = require('../plugins/controllersHelperFunctions');
const fs = require('fs'); const {
const sharp = require('sharp'); deleteCandidateImage,
deleteElectionCandidateImages,
uploadProcessedImage,
} = require('../plugins/electionImageStorage');
////// variables ////// variables
const profileWidth = 300; const profileWidth = 300;
@@ -292,17 +295,7 @@ module.exports.deletOneElection = [
} }
if (deleteElection.candidates.length) { if (deleteElection.candidates.length) {
const targetDir = `./static/uploads/images/election/`; await deleteElectionCandidateImages(deleteElection);
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 deleteElection.deleteOne(); await deleteElection.deleteOne();
@@ -457,16 +450,13 @@ module.exports.addCandidate = [
} }
const imageName = 'candidate_' + Date.now() + '_' + targetElection._id + '.jpg'; const imageName = 'candidate_' + Date.now() + '_' + targetElection._id + '.jpg';
data.profilePic = imageName; data.profilePic = imageName;
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(profileWidth, profileHeight, { imageName,
fit: 'cover' profileWidth,
}) profileHeight,
.toFormat('jpg') profileQuality
.jpeg({ );
quality: profileQuality
})
.toFile(`./static/uploads/images/election/${imageName}`)
targetElection.candidates.push(data); targetElection.candidates.push(data);
@@ -528,29 +518,16 @@ module.exports.updateCandidate = [
return res.status(422).json({validation: {profilePic: {msg: _sr['fa'].format.image}}}); return res.status(422).json({validation: {profilePic: {msg: _sr['fa'].format.image}}});
} }
const imageName = 'candidate_' + Date.now() + '_' + targetElection._id + '.jpg'; const imageName = 'candidate_' + Date.now() + '_' + targetElection._id + '.jpg';
const target = sharp(image.data) await deleteCandidateImage(candidate);
await target
.resize(profileWidth, profileHeight, {
fit: 'cover'
})
.toFormat('jpg')
.jpeg({
quality: profileQuality
})
.toFile(`./static/uploads/images/election/${imageName}`)
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (candidate.profilePic) { profileWidth,
const oldPic = `./static${candidate.profilePic}`; profileHeight,
profileQuality
);
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
candidate.profilePic = imageName; candidate.profilePic = imageName;
} }
@@ -586,15 +563,7 @@ module.exports.deleteCandidate = [
if (!candidate) return res404(res, `کاندید مورد نظر پیدا نشد`); if (!candidate) return res404(res, `کاندید مورد نظر پیدا نشد`);
if (candidate.profilePic) { await deleteCandidateImage(candidate);
const oldPic = `./static${candidate.profilePic}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
await targetElection.candidates.id(candidateId).remove(); await targetElection.candidates.id(candidateId).remove();
+72 -200
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; if (fileType === "video") {
} catch (error) { return uploadGalleryFile("video", file, galleryId, {});
console.log(error);
return null;
} }
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}`;
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; 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
+20 -41
View File
@@ -3,7 +3,6 @@ const {
body, body,
validationResult validationResult
} = require('express-validator'); } = require('express-validator');
const sharp = require('sharp');
const { const {
_sr _sr
} = require('../plugins/serverResponses'); } = require('../plugins/serverResponses');
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol, isLatinCharactersWithSymbol,
generateRandomDigits generateRandomDigits
} = require('../plugins/controllersHelperFunctions'); } = require('../plugins/controllersHelperFunctions');
const fs = require('fs'); const {
deleteLinksImage,
uploadProcessedImage,
} = require('../plugins/linksImageStorage');
////// variables ////// variables
const profileWidth = 50; const profileWidth = 50;
@@ -108,15 +110,13 @@ module.exports.addLinks = [
} }
const imageName = `links_${Date.now()}_${createLinks._id}.${image.mimetype.split('/')[1]}`; const imageName = `links_${Date.now()}_${createLinks._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(profileWidth, profileHeight, { imageName,
fit: 'cover' profileWidth,
}) profileHeight,
.jpeg({ profileQuality
quality: profileQuality );
})
.toFile(`./static/uploads/images/links/${imageName}`);
createLinks.icon = imageName; createLinks.icon = imageName;
@@ -243,28 +243,15 @@ module.exports.updateLinks = [
} }
const imageName = `links_${Date.now()}_${targetLinks._id}.${image.mimetype.split('/')[1]}`; const imageName = `links_${Date.now()}_${targetLinks._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await deleteLinksImage(targetLinks, 'icon');
await target
.resize(profileWidth, profileHeight, {
fit: 'cover'
})
.jpeg({
quality: profileQuality
})
.toFile(`./static/uploads/images/links/${imageName}`);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (targetLinks.icon) { profileWidth,
var oldPic = `./static${targetLinks.icon}`; profileHeight,
profileQuality
if (fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
targetLinks.icon = imageName; targetLinks.icon = imageName;
targetLinks.linksStatus = true; targetLinks.linksStatus = true;
@@ -312,15 +299,7 @@ module.exports.deletOneLinks = [
return res500(res, `شما اجازه حذف این پیوند را ندارید`); return res500(res, `شما اجازه حذف این پیوند را ندارید`);
} }
if (targetLinks.icon) { await deleteLinksImage(targetLinks, 'icon');
var oldPic = `./static${targetLinks.icon}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (error) => {
if (error) return res500(res, error?.message);
});
}
}
/** delete links */ /** delete links */
await targetLinks.deleteOne(); await targetLinks.deleteOne();
+123 -242
View File
@@ -1,6 +1,5 @@
const News = require("../models/News"); const News = require("../models/News");
const { body, validationResult } = require("express-validator"); const { body, validationResult } = require("express-validator");
const sharp = require("sharp");
const { _sr } = require("../plugins/serverResponses"); const { _sr } = require("../plugins/serverResponses");
const { const {
res404, res404,
@@ -10,13 +9,18 @@ const {
const Category = require("../models/Category"); const Category = require("../models/Category");
const Comment = require("../models/Comment"); const Comment = require("../models/Comment");
const User = require("../models/User"); const User = require("../models/User");
const fs = require("fs");
const NewsFile = require("../models/NewsFile"); const NewsFile = require("../models/NewsFile");
const Polls = require("../models/Polls"); const Polls = require("../models/Polls");
const moment = require("moment-jalaali"); const moment = require("moment-jalaali");
const {
getStoredImageName,
deleteNewsImage,
deleteNewsAttachment,
uploadProcessedImage,
uploadNewsAttachment,
} = require("../plugins/newsImageStorage");
////// variables ////// variables
const noCover = "/img/no-cover.jpg";
const coverNewsWidth = 1920; const coverNewsWidth = 1920;
const coverNewsHeight = 1080; const coverNewsHeight = 1080;
const coverQuality = 100; const coverQuality = 100;
@@ -111,31 +115,23 @@ const handleFileUploads = async (req, res, targetNews) => {
if (req.files.cover) { if (req.files.cover) {
const image = req.files.cover; const image = req.files.cover;
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) { if (!validateNewsImage(image)) {
throw new Error(_sr["fa"].format.image); throw new Error(_sr["fa"].format.image);
} }
// حذف کاور قدیمی در صورت وجود await deleteNewsImage(targetNews, "cover");
if (targetNews.cover && targetNews.cover !== noCover) {
const oldCoverPath = `./static/uploads/images/news/${targetNews.cover}`;
if (fs.existsSync(oldCoverPath)) {
fs.unlinkSync(oldCoverPath);
}
}
const imageName = `news_cover_${timestamp}_${targetNews._id}.${ const imageName = `news_cover_${timestamp}_${targetNews._id}.${
image.mimetype.split("/")[1] image.mimetype.split("/")[1]
}`; }`;
try { try {
const target = sharp(image.data); await processAndUploadNewsImage(
await target image.data,
.resize(coverNewsWidth, coverNewsHeight, { imageName,
fit: "cover", coverNewsWidth,
}) coverNewsHeight,
.jpeg({ coverQuality
quality: coverQuality, );
})
.toFile(`./static/uploads/images/news/${imageName}`);
targetNews.cover = imageName; targetNews.cover = imageName;
hasCover = true; hasCover = true;
@@ -143,28 +139,20 @@ const handleFileUploads = async (req, res, targetNews) => {
throw new Error(e?.message); throw new Error(e?.message);
} }
// اگر thumbCover ارسال نشده، از همین تصویر thumbCover بسازیم
if (!req.files.thumbCover) { if (!req.files.thumbCover) {
if (targetNews.thumbCover && targetNews.thumbCover !== noCover) { await deleteNewsImage(targetNews, "thumbCover");
const oldThumbPath = `./static/uploads/images/news/${targetNews.thumbCover}`;
if (fs.existsSync(oldThumbPath)) {
fs.unlinkSync(oldThumbPath);
}
}
const thumbImageName = `news_thumb_${timestamp}_${targetNews._id}.${ const thumbImageName = `news_thumb_${timestamp}_${targetNews._id}.${
image.mimetype.split("/")[1] image.mimetype.split("/")[1]
}`; }`;
try { try {
const target = sharp(image.data); await processAndUploadNewsImage(
await target image.data,
.resize(thumbCoverNewsWidth, thumbCoverNewsHeight, { thumbImageName,
fit: "cover", thumbCoverNewsWidth,
}) thumbCoverNewsHeight,
.jpeg({ thumbCoverQuality
quality: thumbCoverQuality, );
})
.toFile(`./static/uploads/images/news/${thumbImageName}`);
targetNews.thumbCover = thumbImageName; targetNews.thumbCover = thumbImageName;
hasThumb = true; hasThumb = true;
@@ -176,31 +164,23 @@ const handleFileUploads = async (req, res, targetNews) => {
if (req.files.thumbCover) { if (req.files.thumbCover) {
const image = req.files.thumbCover; const image = req.files.thumbCover;
if (!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])) { if (!validateNewsImage(image)) {
throw new Error(_sr["fa"].format.image); throw new Error(_sr["fa"].format.image);
} }
// حذف thumb قدیمی در صورت وجود await deleteNewsImage(targetNews, "thumbCover");
if (targetNews.thumbCover && targetNews.thumbCover !== noCover) {
const oldThumbPath = `./static/uploads/images/news/${targetNews.thumbCover}`;
if (fs.existsSync(oldThumbPath)) {
fs.unlinkSync(oldThumbPath);
}
}
const thumbImageName = `news_thumb_${timestamp}_${targetNews._id}.${ const thumbImageName = `news_thumb_${timestamp}_${targetNews._id}.${
image.mimetype.split("/")[1] image.mimetype.split("/")[1]
}`; }`;
try { try {
const target = sharp(image.data); await processAndUploadNewsImage(
await target image.data,
.resize(thumbCoverNewsWidth, thumbCoverNewsHeight, { thumbImageName,
fit: "cover", thumbCoverNewsWidth,
}) thumbCoverNewsHeight,
.jpeg({ thumbCoverQuality
quality: thumbCoverQuality, );
})
.toFile(`./static/uploads/images/news/${thumbImageName}`);
targetNews.thumbCover = thumbImageName; targetNews.thumbCover = thumbImageName;
hasThumb = true; hasThumb = true;
@@ -208,28 +188,20 @@ const handleFileUploads = async (req, res, targetNews) => {
throw new Error(e?.message); throw new Error(e?.message);
} }
// اگر cover ارسال نشده، از همین تصویر cover بسازیم
if (!req.files.cover) { if (!req.files.cover) {
if (targetNews.cover && targetNews.cover !== noCover) { await deleteNewsImage(targetNews, "cover");
const oldCoverPath = `./static/uploads/images/news/${targetNews.cover}`;
if (fs.existsSync(oldCoverPath)) {
fs.unlinkSync(oldCoverPath);
}
}
const coverImageName = `news_cover_${timestamp}_${targetNews._id}.${ const coverImageName = `news_cover_${timestamp}_${targetNews._id}.${
image.mimetype.split("/")[1] image.mimetype.split("/")[1]
}`; }`;
try { try {
const target = sharp(image.data); await processAndUploadNewsImage(
await target image.data,
.resize(coverNewsWidth, coverNewsHeight, { coverImageName,
fit: "cover", coverNewsWidth,
}) coverNewsHeight,
.jpeg({ coverQuality
quality: coverQuality, );
})
.toFile(`./static/uploads/images/news/${coverImageName}`);
targetNews.cover = coverImageName; targetNews.cover = coverImageName;
hasCover = true; hasCover = true;
@@ -239,14 +211,24 @@ const handleFileUploads = async (req, res, targetNews) => {
} }
} }
// به‌روزرسانی وضعیت خبر - خبر کامل است اگر حداقل یکی از تصاویر آپلود شده باشد یا قبلاً موجود باشد const currentHasCover = getStoredImageName(targetNews, "cover");
const currentHasCover = targetNews.cover && targetNews.cover !== noCover; const currentHasThumb = getStoredImageName(targetNews, "thumbCover");
const currentHasThumb =
targetNews.thumbCover && targetNews.thumbCover !== noCover;
targetNews.newsStatus = targetNews.newsStatus =
hasCover || hasThumb || currentHasCover || currentHasThumb; hasCover || hasThumb || currentHasCover || currentHasThumb;
}; };
const processAndUploadNewsImage = async (
imageData,
imageName,
width,
height,
quality
) => uploadProcessedImage(imageData, imageName, width, height, quality);
const validateNewsImage = (image) => {
return _sr.supportedImageFormats.includes(image.mimetype.split("/")[1]);
};
/////////////////////////////////////////////////////////////////////// admin /////////////////////////////////////////////////////////////////////// admin
module.exports.addNews = [ module.exports.addNews = [
[ [
@@ -482,127 +464,16 @@ module.exports.addNews = [
* if we have pic upload to server * if we have pic upload to server
*/ */
if (req.files) { if (req.files) {
let hasCover = false;
let hasThumb = false;
const timestamp = Date.now();
if (req.files.cover) {
const image = req.files.cover;
if (
!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])
) {
return res.status(422).json({
validation: {
cover: {
msg: _sr["fa"].format.image,
},
},
});
}
const imageName = `news_cover_${timestamp}_${createNews._id}.${
image.mimetype.split("/")[1]
}`;
try { try {
const target = sharp(image.data); await handleFileUploads(req, res, createNews);
await target
.resize(coverNewsWidth, coverNewsHeight, {
fit: "cover",
})
.jpeg({
quality: coverQuality,
})
.toFile(`./static/uploads/images/news/${imageName}`);
createNews.cover = imageName;
hasCover = true;
} catch (e) {
return res500(res, e?.message);
}
// اگر thumbCover ارسال نشده، از همین تصویر thumbCover بسازیم
if (!req.files.thumbCover) {
const thumbImageName = `news_thumb_${timestamp}_${createNews._id}.${
image.mimetype.split("/")[1]
}`;
try {
const target = sharp(image.data);
await target
.resize(thumbCoverNewsWidth, thumbCoverNewsHeight, {
fit: "cover",
})
.jpeg({
quality: thumbCoverQuality,
})
.toFile(`./static/uploads/images/news/${thumbImageName}`);
createNews.thumbCover = thumbImageName;
hasThumb = true;
} catch (e) {
return res500(res, e?.message);
}
}
}
if (req.files.thumbCover) {
const image = req.files.thumbCover;
if (
!_sr.supportedImageFormats.includes(image.mimetype.split("/")[1])
) {
return res.status(422).json({
validation: {
thumbCover: {
msg: _sr["fa"].format.image,
},
},
});
}
const thumbImageName = `news_thumb_${timestamp}_${createNews._id}.${
image.mimetype.split("/")[1]
}`;
try {
const target = sharp(image.data);
await target
.resize(thumbCoverNewsWidth, thumbCoverNewsHeight, {
fit: "cover",
})
.jpeg({
quality: thumbCoverQuality,
})
.toFile(`./static/uploads/images/news/${thumbImageName}`);
createNews.thumbCover = thumbImageName;
hasThumb = true;
} catch (e) {
return res500(res, e?.message);
}
// اگر cover ارسال نشده، از همین تصویر cover بسازیم
if (!req.files.cover) {
const coverImageName = `news_cover_${timestamp}_${createNews._id}.${
image.mimetype.split("/")[1]
}`;
try {
const target = sharp(image.data);
await target
.resize(coverNewsWidth, coverNewsHeight, {
fit: "cover",
})
.jpeg({
quality: coverQuality,
})
.toFile(`./static/uploads/images/news/${coverImageName}`);
createNews.cover = coverImageName;
hasCover = true;
} catch (e) {
return res500(res, e?.message);
}
}
}
// خبر کامل است اگر حداقل یکی از تصاویر آپلود شده باشد
createNews.newsStatus = hasCover || hasThumb;
await createNews.save(); await createNews.save();
} catch (error) {
return res.status(422).json({
validation: {
cover: { msg: error.message },
},
});
}
} }
/** /**
@@ -987,30 +858,18 @@ module.exports.deletOneNews = [
return res500(res, `شما اجازه حذف این خبر را ندارید`); return res500(res, `شما اجازه حذف این خبر را ندارید`);
} }
if (targetNews.cover !== noCover) { await deleteNewsImage(targetNews, "cover");
oldPic = `./static${targetNews.cover}`; await deleteNewsImage(targetNews, "thumbCover");
if (fs.existsSync(oldPic)) { for (const attachment of targetNews.attachments) {
fs.unlink(oldPic, (error) => { const fileName = attachment.get("file", null, { getters: false });
if (error) return res500(res, error?.message); await deleteNewsAttachment(fileName);
});
}
}
if (targetNews.thumbCover !== noCover) {
oldPic = `./static${targetNews.thumbCover}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (error) => {
if (error) return res500(res, error?.message);
});
}
} }
await targetNews.deleteOne(); await targetNews.deleteOne();
await Comment.deleteMany({ modelRef: targetNews._id }); await Comment.deleteMany({ modelRef: targetNews._id });
x;
return res.json(targetNews); return res.json(targetNews);
} catch (error) { } catch (error) {
return res500(res, error); return res500(res, error);
@@ -1021,13 +880,22 @@ module.exports.deletOneNews = [
module.exports.deleteAllNews = [ module.exports.deleteAllNews = [
async (req, res) => { async (req, res) => {
try { try {
const importedNews = await News.find({ imported: true });
for (const news of importedNews) {
await deleteNewsImage(news, "cover");
await deleteNewsImage(news, "thumbCover");
for (const attachment of news.attachments) {
const fileName = attachment.get("file", null, { getters: false });
await deleteNewsAttachment(fileName);
}
}
await News.deleteMany({ imported: true }); await News.deleteMany({ imported: true });
return res.json("ok"); return res.json("ok");
} catch (error) { } catch (error) {
/**
* if we got unkown error show to client
*/
return res500(res, error.message); return res500(res, error.message);
} }
}, },
@@ -1322,52 +1190,65 @@ module.exports.searchForSlider = [
module.exports.addAttachmentToNews = [ module.exports.addAttachmentToNews = [
[body("attTitle").notEmpty().withMessage(_sr["fa"].required.title)], [body("attTitle").notEmpty().withMessage(_sr["fa"].required.title)],
checkValidations(validationResult), checkValidations(validationResult),
(req, res) => { async (req, res) => {
if (!req.files?.file) try {
if (!req.files?.file) {
return res return res
.status(422) .status(422)
.json({ validation: { file: { msg: _sr["fa"].required.file } } }); .json({ validation: { file: { msg: _sr["fa"].required.file } } });
if (req.files?.file.mimetype.split("/")[1] !== "pdf") }
if (req.files.file.mimetype.split("/")[1] !== "pdf") {
return res return res
.status(422) .status(422)
.json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } }); .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } });
}
const news = await News.findById(req.params.newsId);
if (!news) {
return res404(res, "خبر مورد نظر پیدا نشد");
}
const data = { title: req.body.attTitle };
const file = req.files.file;
const fileName = const fileName =
req.body.attTitle.replace(titleFilterRegex, "-") + req.body.attTitle.replace(titleFilterRegex, "-") +
"_" + "_" +
Date.now() + Date.now() +
".pdf"; ".pdf";
file.mv(`./static/uploads/files/newsAttachments/${fileName}`);
data.file = fileName;
News.findById(req.params.newsId, (err, news) => { await uploadNewsAttachment(req.files.file.data, fileName);
if (err || !news) return res404(res, err);
news.attachments.push(data); news.attachments.push({ title: req.body.attTitle, file: fileName });
news.save((err) => { await news.save();
if (err) return res500(res, err);
else return res.json({ message: _sr["fa"].response.success_save }); return res.json({ message: _sr["fa"].response.success_save });
}); } catch (error) {
}); return res500(res, error.message);
}
}, },
]; ];
module.exports.removeAttachmentFromNews = [ module.exports.removeAttachmentFromNews = [
(req, res) => { async (req, res) => {
News.findOne({ "attachments._id": req.params.fileId }, (err, news) => { try {
if (err || !news) return res404(res, err); const news = await News.findOne({ "attachments._id": req.params.fileId });
const file = news.attachments.id(req.params.fileId); if (!news) {
if (!file) return res404(res, "not found"); return res404(res, "not found");
file.remove(); }
fs.unlink(`./static/${file.file}`, (err) => {
if (err) console.log(err); const attachment = news.attachments.id(req.params.fileId);
}); if (!attachment) {
news.save((err) => { return res404(res, "not found");
if (err) return res500(res, err); }
else return res.json({ message: _sr["fa"].response.success_remove });
}); const fileName = attachment.get("file", null, { getters: false });
}); attachment.remove();
await deleteNewsAttachment(fileName);
await news.save();
return res.json({ message: _sr["fa"].response.success_remove });
} catch (error) {
return res500(res, error.message);
}
}, },
]; ];
+35 -45
View File
@@ -4,11 +4,12 @@ const {_sr} = require('../plugins/serverResponses')
const {res404, res500} = require('../plugins/controllersHelperFunctions') const {res404, res500} = require('../plugins/controllersHelperFunctions')
const News = require('../models/News') const News = require('../models/News')
const Category = require('../models/Category') const Category = require('../models/Category')
const sharp = require('sharp') const {
const fs = require('fs') deleteNewsImage,
uploadProcessedImage,
} = require('../plugins/newsImageStorage')
////// variables ////// variables
const noCover = '/img/no-cover.jpg'
const limit = 5; const limit = 5;
const limitDetails = 11; const limitDetails = 11;
const titleFilterRegex = new RegExp('/', 'g') const titleFilterRegex = new RegExp('/', 'g')
@@ -187,19 +188,23 @@ module.exports.addNewsFile = [
}) })
const imageName = `news_${Date.now()}_${createNewsFile._id}.${image.mimetype.split('/')[1]}`; const imageName = `news_${Date.now()}_${createNewsFile._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(coverNewsWidth, coverNewsHeight, {fit: 'cover'}) imageName,
.jpeg({quality: coverQuality}) coverNewsWidth,
.toFile(`./static/uploads/images/news/${imageName}`); coverNewsHeight,
coverQuality
);
await uploadProcessedImage(
image.data,
`thumb_${imageName}`,
thumbCoverNewsWidth,
thumbCoverNewsHeight,
thumbCoverQuality
);
createNewsFile.cover = imageName; createNewsFile.cover = imageName;
await target
.resize(thumbCoverNewsWidth, thumbCoverNewsHeight, {fit: 'cover'})
.jpeg({quality: thumbCoverQuality})
.toFile(`./static/uploads/images/news/thumb_${imageName}`);
createNewsFile.thumbCover = `thumb_${imageName}`; createNewsFile.thumbCover = `thumb_${imageName}`;
await createNewsFile.save(); await createNewsFile.save();
@@ -375,39 +380,24 @@ module.exports.updateNewsFile = [
} }
const imageName = `news_${Date.now()}_${targetNewsFile._id}.${image.mimetype.split('/')[1]}`; const imageName = `news_${Date.now()}_${targetNewsFile._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await deleteNewsImage(targetNewsFile, "cover");
await target await deleteNewsImage(targetNewsFile, "thumbCover");
.resize(coverNewsWidth, coverNewsHeight, {fit: 'cover'})
.jpeg({quality: coverQuality})
.toFile(`./static/uploads/images/news/${imageName}`);
await target await uploadProcessedImage(
.resize(thumbCoverNewsWidth, thumbCoverNewsHeight, {fit: 'cover'}) image.data,
.jpeg({quality: thumbCoverQuality}) imageName,
.toFile(`./static/uploads/images/news/thumb_${imageName}`); coverNewsWidth,
coverNewsHeight,
coverQuality
);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ `thumb_${imageName}`,
if (targetNewsFile.cover !== noCover) { thumbCoverNewsWidth,
let oldPic = `./static${targetNewsFile.cover}`; thumbCoverNewsHeight,
thumbCoverQuality
if (await fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
if (targetNewsFile.thumbCover !== noCover) {
let oldPic = `./static${targetNewsFile.thumbCover}`;
if (await fs.existsSync(oldPic)) {
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
targetNewsFile.cover = imageName; targetNewsFile.cover = imageName;
targetNewsFile.thumbCover = `thumb_${imageName}`; targetNewsFile.thumbCover = `thumb_${imageName}`;
+20 -40
View File
@@ -2,8 +2,10 @@ const Occasion = require('../models/Occasion');
const {body, validationResult} = require('express-validator'); const {body, validationResult} = require('express-validator');
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 {
const sharp = require('sharp'); deleteOccasionImage,
uploadProcessedImage,
} = require('../plugins/occasionImageStorage');
const moment = require('moment-jalaali'); const moment = require('moment-jalaali');
////// variables ////// variables
@@ -63,16 +65,13 @@ module.exports.addOccasion = [
} }
const imageName = `occasion_${addOccasion._id}_${Date.now()}.jpg`; const imageName = `occasion_${addOccasion._id}_${Date.now()}.jpg`;
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(occasionWidth, occasionHeight, { imageName,
fit: 'cover' occasionWidth,
}) occasionHeight,
.toFormat('jpg') occasionQuality
.jpeg({ );
quality: occasionQuality
})
.toFile(`./static/uploads/images/occasion/${imageName}`);
addOccasion.status = true; addOccasion.status = true;
addOccasion.mainCover = imageName; addOccasion.mainCover = imageName;
@@ -132,26 +131,15 @@ module.exports.updateOccasion = [
} }
const imageName = `occasion_${targetOccasion._id}_${Date.now()}.jpg`; const imageName = `occasion_${targetOccasion._id}_${Date.now()}.jpg`;
const target = sharp(image.data) await deleteOccasionImage(targetOccasion, 'mainCover');
await target
.resize(occasionWidth, occasionHeight, {
fit: 'cover'
})
.toFormat('jpg')
.jpeg({
quality: occasionQuality
})
.toFile(`./static/uploads/images/occasion/${imageName}`);
if (targetOccasion.mainCover) { await uploadProcessedImage(
var oldPic = `./static${targetOccasion.mainCover}`; image.data,
imageName,
if (fs.existsSync(oldPic)) { occasionWidth,
fs.unlink(oldPic, (err) => { occasionHeight,
if (err) return res500(res, err?.message); occasionQuality
}); );
}
}
targetOccasion.status = true; targetOccasion.status = true;
targetOccasion.mainCover = imageName; targetOccasion.mainCover = imageName;
@@ -184,15 +172,7 @@ module.exports.deleteOccasion = [
return res404(res, `مناسبت مورد نظر پیدا نشد`); return res404(res, `مناسبت مورد نظر پیدا نشد`);
} }
if (targetOccasion.mainCover) { await deleteOccasionImage(targetOccasion, 'mainCover');
var oldPic = `./static${targetOccasion.mainCover}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
await targetOccasion.deleteOne(); await targetOccasion.deleteOne();
+86 -47
View File
@@ -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 { body, validationResult } = require("express-validator");
const { _sr } = require("../plugins/serverResponses"); const { _sr } = require("../plugins/serverResponses");
const { checkValidations } = require("../plugins/controllersHelperFunctions"); const { checkValidations } = require("../plugins/controllersHelperFunctions");
const Book = require("../models/Book"); 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 = [ module.exports.AddPDF = [
[ [body("title").notEmpty().withMessage(_sr["fa"].required.title)],
body('title')
.notEmpty().withMessage(_sr['fa'].required.title)
],
checkValidations(validationResult), checkValidations(validationResult),
async (req, res) => { async (req, res) => {
let tmpDir;
try { try {
const firstbook = []; const firstbook = [];
if (!req.files?.file) { if (!req.files?.file) {
return res.status(422).json({ validation: { file: { msg: _sr['fa'].required.file } } }); return res
.status(422)
.json({ validation: { file: { msg: _sr["fa"].required.file } } });
} }
if (req.files?.file.mimetype.split('/')[1] !== 'pdf') { if (req.files?.file.mimetype.split("/")[1] !== "pdf") {
return res.status(422).json({ validation: { file: { msg: 'فقط فرمت PDF قابل قبول است' } } }); return res
.status(422)
.json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } });
} }
const book = await Book.findById(req.body.bookId); const book = await Book.findById(req.body.bookId);
const bookId = book ? book._id : null; const bookId = book ? book._id : null;
const data = { title: req.body.title };
const file = req.files.file; const file = req.files.file;
const fileName = req.body.title.replace(Date.now(), '-') + '_' + Date.now() + '.pdf'; const fileName =
const imageName = req.body.title.replace(Date.now(), '-') + '_' + Date.now(); 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}`; const { url: pdfUrl } = await uploadPdf(file.data, fileName);
await file.mv(filePath); tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "book-"));
data.file = fileName; const tmpPdfPath = path.join(tmpDir, fileName);
await fs.writeFile(tmpPdfPath, file.data);
const convertPromise = convertapi.convert('jpg', { const result = await convertapi.convert(
File: filePath "jpg",
}, 'pdf'); { File: tmpPdfPath },
"pdf"
);
const result = await convertPromise; const convertedResponse = await axios.get(result.files[0].url, {
const savedFiles = await result.saveFiles(`./static/uploads/books/${imageName}.jpeg`); responseType: "arraybuffer",
});
const imagePath = `https://ostan-mr.ir/uploads/books/${imageName}.jpeg`; const imagePath = await uploadBookSlide(
Buffer.from(convertedResponse.data),
imageName
);
firstbook.push(imagePath); firstbook.push(imagePath);
if (bookId) { if (bookId) {
const update = await Book.findByIdAndUpdate( const update = await Book.findByIdAndUpdate(
{_id:bookId}, { _id: bookId },
{ $push: { slides: imagePath } }, { $push: { slides: imagePath } },
{ upsert: true } { upsert: true }
); );
return res.status(201).json({ msg: "اسلاید کتاب اضافه شد", Data: update }); return res
} else { .status(201)
.json({ msg: "اسلاید کتاب اضافه شد", Data: update });
}
const newBook = new Book({ const newBook = new Book({
title: req.body.title, title: req.body.title,
uri: `https://ostan-mr.ir/uploads/pdf/${fileName}`, uri: pdfUrl,
slides: firstbook slides: firstbook,
}); });
await newBook.save(); await newBook.save();
return res.status(201).json({ msg: "کتاب ایجاد شد", Data: newBook }); return res.status(201).json({ msg: "کتاب ایجاد شد", Data: newBook });
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return res.status(500).json({ msg: "خطایی در پردازش فایل رخ داد", error: error.message }); return res.status(500).json({
msg: "خطایی در پردازش فایل رخ داد",
error: error.message,
});
} finally {
if (tmpDir) {
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
} }
} }
},
]; ];
module.exports.GetAll = [ module.exports.GetAll = [
async (req, res,next) => { async (req, res, next) => {
try { try {
const Books = await Book.find({}); const Books = await Book.find({});
return res.status(200).json({ Books }); return res.status(200).json({ Books });
} catch (error) { } catch (error) {
next(error) next(error);
}
} }
},
]; ];
module.exports.GetOne = [ module.exports.GetOne = [
async (req, res,next) => { async (req, res, next) => {
try { try {
const id = req.params.id const id = req.params.id;
const book = await Book.findById(id); const book = await Book.findById(id);
return res.status(200).json({ msg: book }); return res.status(200).json({ msg: book });
} catch (error) { } catch (error) {
next(error) next(error);
}
} }
},
]; ];
module.exports.DeleteOne = [ module.exports.DeleteOne = [
async(req,res,next)=>{ async (req, res, next) => {
try { try {
const book = await Book.deleteOne({_id:req.params.id}) const book = await Book.findById(req.params.id);
if(!book){ if (!book) {
return res.status(200).json({ msg: 'کتاب وجود ندارد ' }); return res.status(200).json({ msg: "کتاب وجود ندارد " });
} }
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) { } catch (error) {
next(error) next(error);
} }
} },
] ];
+20 -41
View File
@@ -3,7 +3,6 @@ const {
body, body,
validationResult validationResult
} = require('express-validator'); } = require('express-validator');
const sharp = require('sharp');
const { const {
_sr _sr
} = require('../plugins/serverResponses'); } = require('../plugins/serverResponses');
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol, isLatinCharactersWithSymbol,
generateRandomDigits generateRandomDigits
} = require('../plugins/controllersHelperFunctions'); } = require('../plugins/controllersHelperFunctions');
const fs = require('fs'); const {
deleteSliderImage,
uploadProcessedImage,
} = require('../plugins/sliderImageStorage');
////// variables ////// variables
const profileWidth = 600; const profileWidth = 600;
@@ -113,15 +115,13 @@ module.exports.addSlider = [
} }
const imageName = `slider_${Date.now()}_${createSlider._id}.${image.mimetype.split('/')[1]}`; const imageName = `slider_${Date.now()}_${createSlider._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(profileWidth, profileHeight, { imageName,
fit: 'cover' profileWidth,
}) profileHeight,
.jpeg({ profileQuality
quality: profileQuality );
})
.toFile(`./static/uploads/images/slider/${imageName}`);
createSlider.cover = imageName; createSlider.cover = imageName;
@@ -253,28 +253,15 @@ module.exports.updateSlider = [
} }
const imageName = `slider_${Date.now()}_${targetSlider._id}.${image.mimetype.split('/')[1]}`; const imageName = `slider_${Date.now()}_${targetSlider._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await deleteSliderImage(targetSlider, "cover");
await target
.resize(profileWidth, profileHeight, {
fit: 'cover'
})
.jpeg({
quality: profileQuality
})
.toFile(`./static/uploads/images/slider/${imageName}`);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (targetSlider.cover) { profileWidth,
var oldPic = `./static${targetSlider.cover}`; profileHeight,
profileQuality
if (fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
targetSlider.cover = imageName; targetSlider.cover = imageName;
} catch (e) { } catch (e) {
@@ -322,15 +309,7 @@ module.exports.deletOneSlider = [
return res500(res, `شما اجازه حذف این اسلایدر را ندارید`); return res500(res, `شما اجازه حذف این اسلایدر را ندارید`);
} }
if (targetSlider.cover) { await deleteSliderImage(targetSlider, "cover");
var oldPic = `./static${targetSlider.cover}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (error) => {
if (error) return res500(res, error?.message);
});
}
}
/** delete slider */ /** delete slider */
await targetSlider.deleteOne(); await targetSlider.deleteOne();
+20 -41
View File
@@ -3,7 +3,6 @@ const {
body, body,
validationResult validationResult
} = require('express-validator'); } = require('express-validator');
const sharp = require('sharp');
const { const {
_sr _sr
} = require('../plugins/serverResponses'); } = require('../plugins/serverResponses');
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol, isLatinCharactersWithSymbol,
generateRandomDigits generateRandomDigits
} = require('../plugins/controllersHelperFunctions'); } = require('../plugins/controllersHelperFunctions');
const fs = require('fs'); const {
deleteSliderImage,
uploadProcessedImage,
} = require('../plugins/sliderImageStorage');
////// variables ////// variables
const profileWidth = 880; const profileWidth = 880;
@@ -113,15 +115,13 @@ module.exports.addSlider = [
} }
const imageName = `slider_${Date.now()}_${createSlider._id}.${image.mimetype.split('/')[1]}`; const imageName = `slider_${Date.now()}_${createSlider._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(profileWidth, profileHeight, { imageName,
fit: 'cover' profileWidth,
}) profileHeight,
.jpeg({ profileQuality
quality: profileQuality );
})
.toFile(`./static/uploads/images/slider/${imageName}`);
createSlider.cover = imageName; createSlider.cover = imageName;
@@ -253,28 +253,15 @@ module.exports.updateSlider = [
} }
const imageName = `slider_${Date.now()}_${targetSlider._id}.${image.mimetype.split('/')[1]}`; const imageName = `slider_${Date.now()}_${targetSlider._id}.${image.mimetype.split('/')[1]}`;
try { try {
const target = sharp(image.data) await deleteSliderImage(targetSlider, "cover");
await target
.resize(profileWidth, profileHeight, {
fit: 'cover'
})
.jpeg({
quality: profileQuality
})
.toFile(`./static/uploads/images/slider/${imageName}`);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (targetSlider.cover) { profileWidth,
var oldPic = `./static${targetSlider.cover}`; profileHeight,
profileQuality
if (fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
targetSlider.cover = imageName; targetSlider.cover = imageName;
} catch (e) { } catch (e) {
@@ -322,15 +309,7 @@ module.exports.deletOneSlider = [
return res500(res, `شما اجازه حذف این اسلایدر را ندارید`); return res500(res, `شما اجازه حذف این اسلایدر را ندارید`);
} }
if (targetSlider.cover) { await deleteSliderImage(targetSlider, "cover");
var oldPic = `./static${targetSlider.cover}`;
if (fs.existsSync(oldPic)) {
fs.unlink(oldPic, (error) => {
if (error) return res500(res, error?.message);
});
}
}
/** delete slider */ /** delete slider */
await targetSlider.deleteOne(); await targetSlider.deleteOne();
+28 -65
View File
@@ -4,7 +4,6 @@ const { rateLimit } = require("express-rate-limit");
const bcrypt = require("bcryptjs"); const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken"); const jwt = require("jsonwebtoken");
const crypto = require("crypto"); const crypto = require("crypto");
const sharp = require("sharp");
const { secretKey } = require("./../authentication"); const { secretKey } = require("./../authentication");
const { _sr } = require("../plugins/serverResponses"); const { _sr } = require("../plugins/serverResponses");
const { const {
@@ -14,7 +13,10 @@ const {
isLatinCharactersWithSymbol, isLatinCharactersWithSymbol,
generateRandomDigits, generateRandomDigits,
} = require("../plugins/controllersHelperFunctions"); } = require("../plugins/controllersHelperFunctions");
const fs = require("fs"); const {
deleteUserImage,
uploadProcessedImage,
} = require("../plugins/userImageStorage");
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const Category = require("../models/Category"); const Category = require("../models/Category");
const Logger = require("../models/Logger"); const Logger = require("../models/Logger");
@@ -566,15 +568,13 @@ module.exports.registerAdmin = [
image.mimetype.split("/")[1]; image.mimetype.split("/")[1];
data.profilePic = imageName; data.profilePic = imageName;
try { try {
const target = sharp(image.data); await uploadProcessedImage(
await target image.data,
.resize(profileWidth, profileHeight, { imageName,
fit: "cover", profileWidth,
}) profileHeight,
.jpeg({ profileQuality
quality: profileQuality, );
})
.toFile(`./static/uploads/images/users/${imageName}`);
} catch (e) { } catch (e) {
return res500(res, e?.message); return res500(res, e?.message);
} }
@@ -713,18 +713,7 @@ module.exports.deleteOneUser = [
.json({ message: `شما نمیتوانید این کاربر را حذف کنید` }); .json({ message: `شما نمیتوانید این کاربر را حذف کنید` });
} }
/** 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);
});
}
}
/** delete user */ /** delete user */
await user.deleteOne(); await user.deleteOne();
@@ -922,28 +911,15 @@ module.exports.updateUser = [
image.mimetype.split("/")[1]; image.mimetype.split("/")[1];
try { try {
const target = sharp(image.data); await deleteUserImage(user, "profilePic");
await target
.resize(profileWidth, profileHeight, {
fit: "cover",
})
.jpeg({
quality: profileQuality,
})
.toFile(`./static/uploads/images/users/${imageName}`);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (user.profilePic) { profileWidth,
var oldPic = `./static${user.profilePic}`; profileHeight,
profileQuality
if (fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
} catch (e) { } catch (e) {
return res500(res, e?.message); return res500(res, e?.message);
} }
@@ -1129,28 +1105,15 @@ module.exports.updateProfile = [
image.mimetype.split("/")[1]; image.mimetype.split("/")[1];
try { try {
const target = sharp(image.data); await deleteUserImage(user, "profilePic");
await target
.resize(profileWidth, profileHeight, {
fit: "cover",
})
.jpeg({
quality: profileQuality,
})
.toFile(`./static/uploads/images/users/${imageName}`);
/** await uploadProcessedImage(
* if exist old pic delete it from file image.data,
*/ imageName,
if (user.profilePic) { profileWidth,
var oldPic = `./static${user.profilePic}`; profileHeight,
profileQuality
if (fs.existsSync(oldPic)) { );
fs.unlink(oldPic, (err) => {
if (err) return res500(res, err?.message);
});
}
}
} catch (e) { } catch (e) {
return res500(res, e?.message); return res500(res, e?.message);
} }
+8 -8
View File
@@ -2,8 +2,7 @@ const YearBanner = require('../models/YearBanner');
const {body, validationResult, checkSchema} = require('express-validator'); const {body, validationResult, checkSchema} = require('express-validator');
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 {uploadProcessedImage} = require('../plugins/yearBannerImageStorage');
const sharp = require('sharp');
////// variables ////// variables
const yearBannerWidth = 1920 const yearBannerWidth = 1920
@@ -51,12 +50,13 @@ module.exports.updateYearBanner = [
imageName = `mainPage_cover.jpg`; imageName = `mainPage_cover.jpg`;
const target = sharp(image.data) await uploadProcessedImage(
await target image.data,
.resize(yearBannerWidth, yearBannerHeight, {fit: 'cover'}) imageName,
.toFormat('jpg') yearBannerWidth,
.jpeg({quality: yearBannerQuality}) yearBannerHeight,
.toFile(`./static/uploads/images/mainCover/${imageName}`); yearBannerQuality
);
} }
if (yearBanner) { if (yearBanner) {
+2 -1
View File
@@ -7,7 +7,8 @@
// const databaseURL = 'mongodb://ostanmri_user:admin1234@localhost:27017/ostanmri_database' // const databaseURL = 'mongodb://ostanmri_user:admin1234@localhost:27017/ostanmri_database'
const databaseURL = const databaseURL =
// "mongodb://root:f2d63313cc3ed6ff@srv-captain--ostandari-db:27017/ostan_db?authSource=admin"; // "mongodb://root:f2d63313cc3ed6ff@srv-captain--ostandari-db:27017/ostan_db?authSource=admin";
"mongodb://root:f2d63313cc3ed6ff@89.32.249.24:50123/ostan_db?authSource=admin"; "mongodb://root:e9a15xftSaqNgR1fNMSUEHVqYDVh5XiDSW8Dt9sG1UyHvOE6nSTtQguaO4vor4Fp@noxkpqjq8ufxq969ut0v5p3m:27017/ostan_db?directConnection=true&authSource=admin"
// "mongodb://root:f2d63313cc3ed6ff@89.32.249.24:50123/ostan_db?authSource=admin";
//const databaseURL = 'mongodb://danakcorp:danakcorp%212024@ostan-mr.ir:27017/ostan_db?serverSelectionTimeoutMS=5000&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-1' //const databaseURL = 'mongodb://danakcorp:danakcorp%212024@ostan-mr.ir:27017/ostan_db?serverSelectionTimeoutMS=5000&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-1'
// const databaseURL = 'mongodb://ostanmri_user:admin1234@91.98.96.174:27017/ostanmri_database?serverSelectionTimeoutMS=5000&connectTimeoutMS=10000&authSource=ostanmri_database' // const databaseURL = 'mongodb://ostanmri_user:admin1234@91.98.96.174:27017/ostanmri_database?serverSelectionTimeoutMS=5000&connectTimeoutMS=10000&authSource=ostanmri_database'
+2
View File
@@ -1,3 +1,5 @@
require("dotenv").config();
const express = require("express"); const express = require("express");
const database = require("./database"); const database = require("./database");
const fileUpload = require("express-fileupload"); const fileUpload = require("express-fileupload");
+22 -7
View File
@@ -1,16 +1,31 @@
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 pdfUri(uri) {
function PDF(pdf) { if (!uri) return null;
if (pdf) return '/uploads/files/ReadOnly-PDF' + pdf 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({ const BookSchema = mongoose.Schema({
title: String, title: String,
uri: String, uri: { type: String, get: pdfUri, default: null },
slides:{ slides: {
default:[], default: [],
type:Array type: [String],
get: (slides) => (slides || []).map(slideUrl),
} }
}); });
+14 -4
View File
@@ -1,4 +1,5 @@
const mongoose = require("mongoose"); const mongoose = require("mongoose");
const s3Storage = require("../plugins/s3Storage");
function autoPopulateSubs(next) { function autoPopulateSubs(next) {
this.populate({ this.populate({
@@ -14,12 +15,21 @@ function autoPopulateSubs(next) {
} }
function file(file) { 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) { function image(img) {
if (img) return "/uploads/images/category/" + img; if (!img) return "/img/no-cover.jpg";
else 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({ const CategoryTermsFile = mongoose.Schema({
@@ -71,7 +81,7 @@ const CategorySchema = mongoose.Schema({
managerName: String, managerName: String,
managerDescription: String, managerDescription: String,
/** if category was shafafiat this icon is for that */ /** if category was shafafiat this icon is for that */
icon: String, icon: { type: String, get: shafafiatIcon, default: null },
}); });
CategorySchema.index({ parent: 1, childs: 1 }); CategorySchema.index({ parent: 1, childs: 1 });
+4 -1
View File
@@ -1,7 +1,10 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const s3Storage = require('../plugins/s3Storage');
function image(img) { 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}`);
} }
+4 -1
View File
@@ -1,8 +1,11 @@
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 image(img) { 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({ const CandidatesSchema = mongoose.Schema({
+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({
+4 -1
View File
@@ -1,7 +1,10 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const s3Storage = require('../plugins/s3Storage');
function image(img) { 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({ const LinksSchema = mongoose.Schema({
+9 -5
View File
@@ -1,17 +1,21 @@
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 image(img) { function image(img) {
if (img) return '/uploads/images/news/' + img if (!img) return '/img/no-cover.jpg';
else return '/img/no-cover.jpg' if (img.startsWith('http://') || img.startsWith('https://')) return img;
return s3Storage.getPublicUrl(`uploads/images/news/${img}`);
} }
function file(file) { function attachmentFile(file) {
return '/uploads/files/newsAttachments/' + file if (!file) return null;
if (file.startsWith('http://') || file.startsWith('https://')) return file;
return s3Storage.getPublicUrl(`uploads/files/newsAttachments/${file}`);
} }
const NewsAttachmentSchema = mongoose.Schema({ const NewsAttachmentSchema = mongoose.Schema({
file: {type: String, get: file}, file: {type: String, get: attachmentFile},
title: String title: String
}) })
+4 -2
View File
@@ -1,9 +1,11 @@
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 image(img) { function image(img) {
if (img) return '/uploads/images/news/' + img if (!img) return '/img/no-cover.jpg';
else return '/img/no-cover.jpg' if (img.startsWith('http://') || img.startsWith('https://')) return img;
return s3Storage.getPublicUrl(`uploads/images/news/${img}`);
} }
const NewsFileSchema = mongoose.Schema({ const NewsFileSchema = mongoose.Schema({
+4 -1
View File
@@ -1,8 +1,11 @@
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 image(img) { 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({ const OccasionSchema = mongoose.Schema({
+4 -1
View File
@@ -1,8 +1,11 @@
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 image(img) { 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({ const SliderSchema = mongoose.Schema({
+4 -1
View File
@@ -1,8 +1,11 @@
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 image(img) { 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({ const AdminSchema = mongoose.Schema({
+4 -1
View File
@@ -1,7 +1,10 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const s3Storage = require('../plugins/s3Storage');
function image(img) { 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({ const YearBannerSchema = mongoose.Schema({
+33
View File
@@ -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,
};
+48
View File
@@ -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,
};
+94
View File
@@ -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,
};
+52
View File
@@ -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,
};
+58
View File
@@ -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,
};
+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,
};
+46
View File
@@ -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,
};
+74
View File
@@ -0,0 +1,74 @@
const sharp = require("sharp");
const s3Storage = require("./s3Storage");
const NEWS_IMAGE_PREFIX = "uploads/images/news";
const NEWS_ATTACHMENT_PREFIX = "uploads/files/newsAttachments";
const getStoredImageName = (doc, field) =>
doc.get(field, null, { getters: false });
const deleteNewsImage = async (doc, field) => {
const fileName = getStoredImageName(doc, field);
if (!fileName) return;
try {
await s3Storage.deleteObject(`${NEWS_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(
`${NEWS_IMAGE_PREFIX}/${imageName}`,
buffer,
"image/jpeg"
);
return imageName;
};
const uploadNewsAttachment = async (
fileData,
fileName,
contentType = "application/pdf"
) => {
await s3Storage.uploadBuffer(
`${NEWS_ATTACHMENT_PREFIX}/${fileName}`,
fileData,
contentType
);
return fileName;
};
const deleteNewsAttachment = async (fileName) => {
if (!fileName) return;
try {
await s3Storage.deleteObject(`${NEWS_ATTACHMENT_PREFIX}/${fileName}`);
} catch (error) {
console.log(`Failed to delete attachment from S3:`, error?.message);
}
};
module.exports = {
NEWS_IMAGE_PREFIX,
NEWS_ATTACHMENT_PREFIX,
getStoredImageName,
deleteNewsImage,
uploadProcessedImage,
uploadNewsAttachment,
deleteNewsAttachment,
};
+46
View File
@@ -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,
};
+140
View File
@@ -0,0 +1,140 @@
const {
S3Client,
PutObjectCommand,
DeleteObjectCommand,
ListObjectsV2Command,
GetObjectCommand,
} = require("@aws-sdk/client-s3");
const config = {
bucket: process.env.BUCKET_NAME,
region: process.env.BUCKET_REGION || "us-west-2",
endpoint: process.env.PARSPACK_S3_ENDPOINT,
accessKeyId: process.env.BUCKET_ACCESS_KEY,
secretAccessKey: process.env.BUCKET_SECRET_KEY,
publicUrl: process.env.BUCKET_PUBLIC_URL || process.env.PARSPACK_S3_ENDPOINT,
};
let s3Client = null;
const isConfigured = () =>
Boolean(
config.bucket &&
config.endpoint &&
config.accessKeyId &&
config.secretAccessKey
);
const getClient = () => {
if (!isConfigured()) {
throw new Error("S3 storage is not configured");
}
if (!s3Client) {
s3Client = new S3Client({
region: config.region,
endpoint: config.endpoint,
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
forcePathStyle: true,
});
}
return s3Client;
};
const normalizeKey = (key) => key.replace(/^\/+/, "");
const getPublicUrl = (key) => {
const normalizedKey = normalizeKey(key);
const baseUrl = (config.publicUrl || config.endpoint).replace(/\/+$/, "");
return `${baseUrl}/${normalizedKey}`;
};
const uploadBuffer = async (
key,
buffer,
contentType = "application/octet-stream"
) => {
const normalizedKey = normalizeKey(key);
await getClient().send(
new PutObjectCommand({
Bucket: config.bucket,
Key: normalizedKey,
Body: buffer,
ContentType: contentType,
ACL: "public-read",
})
);
return {
key: normalizedKey,
url: getPublicUrl(normalizedKey),
};
};
const deleteObject = async (key) => {
const normalizedKey = normalizeKey(key);
await getClient().send(
new DeleteObjectCommand({
Bucket: config.bucket,
Key: normalizedKey,
})
);
};
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,
};
+46
View File
@@ -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,
};
+12 -7
View File
@@ -1,18 +1,24 @@
const axios = require("axios"); const axios = require("axios");
const SMS_API_KEY = "QkkNxhyXZ6GtEf1soOTtikomO3mA4LaNQDH8mol8huDIwh00";
module.exports.sms = async (phoneNumber, message) => { module.exports.sms = async (phoneNumber, message) => {
const smsService = new SmsService("QkkNxhyXZ6GtEf1soOTtikomO3mA4LaNQDH8mol8huDIwh00"); const smsService = new SmsService(SMS_API_KEY);
return smsService.sendMessage(phoneNumber, message); return smsService.sendMessage(phoneNumber, message);
}; };
/** Sends login/verification OTP via sms.ir template (verify endpoint). */
module.exports.sendOtpSms = async (phoneNumber, otp) => {
const smsService = new SmsService(SMS_API_KEY);
return smsService.sendOTPSms(otp, phoneNumber);
};
class SmsService { class SmsService {
constructor(apiKey) { constructor(apiKey) {
this.API_URL = "https://api.sms.ir/v1"; this.API_URL = "https://api.sms.ir/v1";
this.OTP_PATTERN = "83604"; this.OTP_PATTERN = "628767";
this.SMS_API_KEY = apiKey; this.SMS_API_KEY = apiKey;
} }
//******************************** */
//******************************** */
async sendMessage(phone, message) { async sendMessage(phone, message) {
try { try {
@@ -32,7 +38,7 @@ class SmsService {
console.error("Error in sending SMS", error); console.error("Error in sending SMS", error);
} }
} }
//******************************** */
async getLineNumber() { async getLineNumber() {
try { try {
const { data } = await axios.get(`${this.API_URL}/line`, { const { data } = await axios.get(`${this.API_URL}/line`, {
@@ -45,7 +51,6 @@ class SmsService {
console.error("Error in getting line number", error); console.error("Error in getting line number", error);
} }
} }
//******************************** */
async sendOTPSms(otp, phone) { async sendOTPSms(otp, phone) {
const smsData = { const smsData = {
+46
View File
@@ -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,
};
+46
View File
@@ -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,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
# CKEditor 4 [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20CKEditor%204%20on%20npm&url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fckeditor4)
[![GitHub tag](https://img.shields.io/github/tag/ckeditor/ckeditor4-releases.svg)](https://github.com/ckeditor/ckeditor4-releases)
[![Dependencies](https://img.shields.io/david/ckeditor/ckeditor4.svg)](https://david-dm.org/ckeditor/ckeditor4)
[![Dev dependencies](https://img.shields.io/david/dev/ckeditor/ckeditor4.svg)](https://david-dm.org/ckeditor/ckeditor4?type=dev)
[![Join newsletter](https://img.shields.io/badge/join-newsletter-00cc99.svg)](http://eepurl.com/c3zRPr)
[![Follow twitter](https://img.shields.io/badge/follow-twitter-00cc99.svg)](https://twitter.com/ckeditor)
A highly configurable WYSIWYG HTML editor with hundreds of features, from creating rich text content with captioned images, videos, tables, or media embeds to pasting from Word and drag&drop image upload.
Supports a broad range of browsers, including legacy ones.
![CKEditor 4 screenshot](https://c.cksource.com/a/1/img/npm/ckeditor4.png)
## Getting Started
```
npm install --save ckeditor4
```
Use it on your website:
```html
<div id="editor">
<p>This is the editor content.</p>
</div>
<script src="./node_modules/ckeditor4/ckeditor.js"></script>
<script>
CKEDITOR.replace( 'editor' );
</script>
```
You can also load CKEditor 4 using [CDN](https://cdn.ckeditor.com/#ckeditor4).
## Features
* Over 500 plugins in the [Add-ons Repository](https://ckeditor.com/cke4/addons).
* Pasting from Microsoft Word and Excel.
* Drag&drop image uploads.
* Media embeds to insert videos, tweets, maps, slideshows.
* Powerful clipboard integration.
* Content quality control with Advanced Content Filter.
* Extensible widget system.
* Custom table selection.
* Accessibility conforming to WCAG and Section 508.
* Over 60 localizations available with full RTL support.
## Presets
The CKEditor 4 npm package comes in the `standard-all` preset, so it includes all official CKEditor plugins, with those from the [standard package](https://sdk.ckeditor.com/samples/standardpreset.html) active by default.
## Further Resources
* [CKEditor 4 demo](https://ckeditor.com/ckeditor-4/)
* [Documentation](https://ckeditor.com/docs/ckeditor4/latest/)
* [API documentation](https://ckeditor.com/docs/ckeditor4/latest/api/index.html)
* [Configuration reference](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html)
* [CKEditor SDK with more samples](https://sdk.ckeditor.com/)
If you are looking for CKEditor 5, here's a link to the relevant npm package: <https://www.npmjs.com/package/ckeditor5>
## Browser Support
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome (Android) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari-ios/safari-ios_48x48.png" alt="iOS Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>iOS Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------- | --------- | --------- | --------- | --------- | --------- | --------- |
| IE8, IE9, IE10, IE11, Edge| latest version| latest version| latest version| latest version| latest version| latest version
Find out more in the [Browser Compatibility guide](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_browsers.html#officially-supported-browsers).
## Contribute
If you would like to help maintain the project, follow the [Contribution instructions](https://github.com/ckeditor/ckeditor4/blob/master/.github/CONTRIBUTING.md).
## License
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.md or <https://ckeditor.com/legal/ckeditor-oss-license>
+10
View File
@@ -0,0 +1,10 @@
# Reporting a security issues
If you believe you have found a security issue in the CKEditor 4 software, please contact us immediately.
When reporting a potential security problem, please bear this in mind:
* Make sure to provide as many details as possible about the vulnerability.
* Please do not disclose publicly any security issues until we fix them and publish security releases.
Contact the security team at security@cksource.com. As soon as we receive the security report, we will work promptly to confirm the issue and then to provide a security fix.
+10
View File
@@ -0,0 +1,10 @@
/*
Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
(function(a){if("undefined"==typeof a)throw Error("jQuery should be loaded before CKEditor jQuery adapter.");if("undefined"==typeof CKEDITOR)throw Error("CKEditor should be loaded before CKEditor jQuery adapter.");CKEDITOR.config.jqueryOverrideVal="undefined"==typeof CKEDITOR.config.jqueryOverrideVal?!0:CKEDITOR.config.jqueryOverrideVal;a.extend(a.fn,{ckeditorGet:function(){var a=this.eq(0).data("ckeditorInstance");if(!a)throw"CKEditor is not initialized yet, use ckeditor() with a callback.";return a},
ckeditor:function(g,e){if(!CKEDITOR.env.isCompatible)throw Error("The environment is incompatible.");if(!a.isFunction(g)){var m=e;e=g;g=m}var k=[];e=e||{};this.each(function(){var b=a(this),c=b.data("ckeditorInstance"),f=b.data("_ckeditorInstanceLock"),h=this,l=new a.Deferred;k.push(l.promise());if(c&&!f)g&&g.apply(c,[this]),l.resolve();else if(f)c.once("instanceReady",function(){setTimeout(function d(){c.element?(c.element.$==h&&g&&g.apply(c,[h]),l.resolve()):setTimeout(d,100)},0)},null,null,9999);
else{if(e.autoUpdateElement||"undefined"==typeof e.autoUpdateElement&&CKEDITOR.config.autoUpdateElement)e.autoUpdateElementJquery=!0;e.autoUpdateElement=!1;b.data("_ckeditorInstanceLock",!0);c=a(this).is("textarea")?CKEDITOR.replace(h,e):CKEDITOR.inline(h,e);b.data("ckeditorInstance",c);c.on("instanceReady",function(e){var d=e.editor;setTimeout(function n(){if(d.element){e.removeListener();d.on("dataReady",function(){b.trigger("dataReady.ckeditor",[d])});d.on("setData",function(a){b.trigger("setData.ckeditor",
[d,a.data])});d.on("getData",function(a){b.trigger("getData.ckeditor",[d,a.data])},999);d.on("destroy",function(){b.trigger("destroy.ckeditor",[d])});d.on("save",function(){a(h.form).submit();return!1},null,null,20);if(d.config.autoUpdateElementJquery&&b.is("textarea")&&a(h.form).length){var c=function(){b.ckeditor(function(){d.updateElement()})};a(h.form).submit(c);a(h.form).bind("form-pre-serialize",c);b.bind("destroy.ckeditor",function(){a(h.form).unbind("submit",c);a(h.form).unbind("form-pre-serialize",
c)})}d.on("destroy",function(){b.removeData("ckeditorInstance")});b.removeData("_ckeditorInstanceLock");b.trigger("instanceReady.ckeditor",[d]);g&&g.apply(d,[h]);l.resolve()}else setTimeout(n,100)},0)},null,null,9999)}});var f=new a.Deferred;this.promise=f.promise();a.when.apply(this,k).then(function(){f.resolve()});this.editor=this.eq(0).data("ckeditorInstance");return this}});CKEDITOR.config.jqueryOverrideVal&&(a.fn.val=CKEDITOR.tools.override(a.fn.val,function(g){return function(e){if(arguments.length){var m=
this,k=[],f=this.each(function(){var b=a(this),c=b.data("ckeditorInstance");if(b.is("textarea")&&c){var f=new a.Deferred;c.setData(e,function(){f.resolve()});k.push(f.promise());return!0}return g.call(b,e)});if(k.length){var b=new a.Deferred;a.when.apply(this,k).done(function(){b.resolveWith(m)});return b.promise()}return f}var f=a(this).eq(0),c=f.data("ckeditorInstance");return f.is("textarea")&&c?c.getData():g.call(f)}}))})(window.jQuery);
Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

@@ -0,0 +1,16 @@
{
"bender": {
"port": 9001
},
"server": {
"port": 9002
},
"paths": {
"ckeditor4": "../ckeditor4/",
"runner": "./src/runner.html"
},
"browsers": {
"linux": [ "chrome", "firefox" ],
"macos": [ "safari" ]
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "ckeditor",
"description": "JavaScript WYSIWYG web text editor.",
"keywords": [ "ckeditor4", "ckeditor", "fckeditor", "editor", "wysiwyg", "html", "richtext", "text", "javascript" ],
"authors": "CKSource (https://cksource.com/)",
"license": "For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license/.",
"homepage": "https://ckeditor.com",
"main": "./ckeditor.js",
"moduleType": "globals"
}
+1297
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
{
"name": "ckeditor/ckeditor",
"description": "JavaScript WYSIWYG web text editor.",
"type": "library",
"keywords": [ "ckeditor4", "ckeditor", "fckeditor", "editor", "wysiwyg", "html", "richtext", "text", "javascript" ],
"homepage": "https://ckeditor.com/ckeditor-4/",
"license": [ "GPL-2.0+", "LGPL-2.1+", "MPL-1.1+" ],
"authors": [
{
"name": "CKSource",
"homepage": "https://cksource.com"
}
],
"support": {
"issues": "https://github.com/ckeditor/ckeditor4/issues",
"forum": "https://stackoverflow.com/tags/ckeditor",
"wiki": "https://ckeditor.com/docs/ckeditor4/latest/",
"source": "https://github.com/ckeditor/ckeditor4"
}
}
+38
View File
@@ -0,0 +1,38 @@
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see https://ckeditor.com/legal/ckeditor-oss-license
*/
CKEDITOR.editorConfig = function( config ) {
// Define changes to default configuration here.
// For complete reference see:
// https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html
// The toolbar groups arrangement, optimized for two toolbar rows.
config.toolbarGroups = [
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'forms' },
{ name: 'tools' },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'others' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'styles' },
{ name: 'colors' },
{ name: 'about' }
];
// Remove some buttons provided by the standard plugins, which are
// not needed in the Standard(s) toolbar.
config.removeButtons = 'Underline,Subscript,Superscript';
// Set the most common block elements.
config.format_tags = 'p;h1;h2;h3;pre';
// Simplify the dialog windows.
config.removeDialogTabs = 'image:advanced;link:advanced';
};
+208
View File
@@ -0,0 +1,208 @@
/*
Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
body
{
/* Font */
/* Emoji fonts are added to visualise them nicely in Internet Explorer. */
font-family: sans-serif, Arial, Verdana, "Trebuchet MS", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 12px;
/* Text color */
color: #333;
/* Remove the background color to make it transparent. */
background-color: #fff;
margin: 20px;
}
.cke_editable
{
font-size: 13px;
line-height: 1.6;
/* Fix for missing scrollbars with RTL texts. (#10488) */
word-wrap: break-word;
}
blockquote
{
font-style: italic;
font-family: Georgia, Times, "Times New Roman", serif;
padding: 2px 0;
border-style: solid;
border-color: #ccc;
border-width: 0;
}
.cke_contents_ltr blockquote
{
padding-left: 20px;
padding-right: 8px;
border-left-width: 5px;
}
.cke_contents_rtl blockquote
{
padding-left: 8px;
padding-right: 20px;
border-right-width: 5px;
}
a
{
color: #0782C1;
}
ol,ul,dl
{
/* IE7: reset rtl list margin. (#7334) */
*margin-right: 0px;
/* Preserved spaces for list items with text direction different than the list. (#6249,#8049)*/
padding: 0 40px;
}
h1,h2,h3,h4,h5,h6
{
font-weight: normal;
line-height: 1.2;
}
hr
{
border: 0px;
border-top: 1px solid #ccc;
}
img.right
{
border: 1px solid #ccc;
float: right;
margin-left: 15px;
padding: 5px;
}
img.left
{
border: 1px solid #ccc;
float: left;
margin-right: 15px;
padding: 5px;
}
pre
{
white-space: pre-wrap; /* CSS 2.1 */
word-wrap: break-word; /* IE7 */
-moz-tab-size: 4;
tab-size: 4;
}
.marker
{
background-color: Yellow;
}
span[lang]
{
font-style: italic;
}
figure
{
text-align: center;
outline: solid 1px #ccc;
background: rgba(0,0,0,0.05);
padding: 10px;
margin: 10px 20px;
display: inline-block;
}
figure > figcaption
{
text-align: center;
display: block; /* For IE8 */
}
a > img {
padding: 1px;
margin: 1px;
border: none;
outline: 1px solid #0782C1;
}
/* Widget Styles */
.code-featured
{
border: 5px solid red;
}
.math-featured
{
padding: 20px;
box-shadow: 0 0 2px rgba(200, 0, 0, 1);
background-color: rgba(255, 0, 0, 0.05);
margin: 10px;
}
.image-clean
{
border: 0;
background: none;
padding: 0;
}
.image-clean > figcaption
{
font-size: .9em;
text-align: right;
}
.image-grayscale
{
background-color: white;
color: #666;
}
.image-grayscale img, img.image-grayscale
{
filter: grayscale(100%);
}
.embed-240p
{
max-width: 426px;
max-height: 240px;
margin:0 auto;
}
.embed-360p
{
max-width: 640px;
max-height: 360px;
margin:0 auto;
}
.embed-480p
{
max-width: 854px;
max-height: 480px;
margin:0 auto;
}
.embed-720p
{
max-width: 1280px;
max-height: 720px;
margin:0 auto;
}
.embed-1080p
{
max-width: 1920px;
max-height: 1080px;
margin:0 auto;
}
@@ -0,0 +1,63 @@
Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
af.js Found: 62 Missing: 4
ar.js Found: 51 Missing: 15
bg.js Found: 58 Missing: 8
bn.js Found: 40 Missing: 26
bs.js Found: 29 Missing: 37
ca.js Found: 61 Missing: 5
cs.js Found: 66 Missing: 0
cy.js Found: 66 Missing: 0
da.js Found: 66 Missing: 0
de.js Found: 66 Missing: 0
el.js Found: 59 Missing: 7
en-au.js Found: 38 Missing: 28
en-ca.js Found: 37 Missing: 29
en-gb.js Found: 61 Missing: 5
eo.js Found: 66 Missing: 0
es.js Found: 66 Missing: 0
et.js Found: 66 Missing: 0
eu.js Found: 48 Missing: 18
fa.js Found: 66 Missing: 0
fi.js Found: 66 Missing: 0
fo.js Found: 66 Missing: 0
fr-ca.js Found: 42 Missing: 24
fr.js Found: 66 Missing: 0
gl.js Found: 40 Missing: 26
gu.js Found: 66 Missing: 0
he.js Found: 66 Missing: 0
hi.js Found: 43 Missing: 23
hr.js Found: 66 Missing: 0
hu.js Found: 63 Missing: 3
is.js Found: 41 Missing: 25
it.js Found: 66 Missing: 0
ja.js Found: 62 Missing: 4
ka.js Found: 62 Missing: 4
km.js Found: 40 Missing: 26
ko.js Found: 40 Missing: 26
lt.js Found: 66 Missing: 0
lv.js Found: 40 Missing: 26
mk.js Found: 0 Missing: 66
mn.js Found: 40 Missing: 26
ms.js Found: 39 Missing: 27
nb.js Found: 66 Missing: 0
nl.js Found: 65 Missing: 1
no.js Found: 66 Missing: 0
pl.js Found: 66 Missing: 0
pt-br.js Found: 66 Missing: 0
pt.js Found: 52 Missing: 14
ro.js Found: 61 Missing: 5
ru.js Found: 66 Missing: 0
sk.js Found: 49 Missing: 17
sl.js Found: 48 Missing: 18
sr-latn.js Found: 40 Missing: 26
sr.js Found: 40 Missing: 26
sv.js Found: 62 Missing: 4
th.js Found: 40 Missing: 26
tr.js Found: 66 Missing: 0
ug.js Found: 66 Missing: 0
uk.js Found: 66 Missing: 0
vi.js Found: 66 Missing: 0
zh-cn.js Found: 66 Missing: 0
zh.js Found: 58 Missing: 8
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More