diff --git a/server/controllers/newsController.js b/server/controllers/newsController.js index 53f8439..16c5571 100644 --- a/server/controllers/newsController.js +++ b/server/controllers/newsController.js @@ -9,14 +9,15 @@ const { const Category = require("../models/Category"); const Comment = require("../models/Comment"); const User = require("../models/User"); -const fs = require("fs"); const NewsFile = require("../models/NewsFile"); const Polls = require("../models/Polls"); const moment = require("moment-jalaali"); const { getStoredImageName, deleteNewsImage, + deleteNewsAttachment, uploadProcessedImage, + uploadNewsAttachment, } = require("../plugins/newsImageStorage"); ////// variables @@ -463,112 +464,16 @@ module.exports.addNews = [ * if we have pic upload to server */ if (req.files) { - let hasCover = false; - let hasThumb = false; - const timestamp = Date.now(); - - if (req.files.cover) { - const image = req.files.cover; - if (!validateNewsImage(image)) { - return res.status(422).json({ - validation: { - cover: { - msg: _sr["fa"].format.image, - }, - }, - }); - } - const imageName = `news_cover_${timestamp}_${createNews._id}.${ - image.mimetype.split("/")[1] - }`; - try { - await processAndUploadNewsImage( - image.data, - imageName, - coverNewsWidth, - coverNewsHeight, - coverQuality - ); - - createNews.cover = imageName; - hasCover = true; - } catch (e) { - return res500(res, e?.message); - } - - if (!req.files.thumbCover) { - const thumbImageName = `news_thumb_${timestamp}_${createNews._id}.${ - image.mimetype.split("/")[1] - }`; - try { - await processAndUploadNewsImage( - image.data, - thumbImageName, - thumbCoverNewsWidth, - thumbCoverNewsHeight, - thumbCoverQuality - ); - - createNews.thumbCover = thumbImageName; - hasThumb = true; - } catch (e) { - return res500(res, e?.message); - } - } + try { + await handleFileUploads(req, res, createNews); + await createNews.save(); + } catch (error) { + return res.status(422).json({ + validation: { + cover: { msg: error.message }, + }, + }); } - - if (req.files.thumbCover) { - const image = req.files.thumbCover; - if (!validateNewsImage(image)) { - return res.status(422).json({ - validation: { - thumbCover: { - msg: _sr["fa"].format.image, - }, - }, - }); - } - const thumbImageName = `news_thumb_${timestamp}_${createNews._id}.${ - image.mimetype.split("/")[1] - }`; - try { - await processAndUploadNewsImage( - image.data, - thumbImageName, - thumbCoverNewsWidth, - thumbCoverNewsHeight, - thumbCoverQuality - ); - - createNews.thumbCover = thumbImageName; - hasThumb = true; - } catch (e) { - return res500(res, e?.message); - } - - if (!req.files.cover) { - const coverImageName = `news_cover_${timestamp}_${createNews._id}.${ - image.mimetype.split("/")[1] - }`; - try { - await processAndUploadNewsImage( - image.data, - coverImageName, - coverNewsWidth, - coverNewsHeight, - coverQuality - ); - - createNews.cover = coverImageName; - hasCover = true; - } catch (e) { - return res500(res, e?.message); - } - } - } - - createNews.newsStatus = hasCover || hasThumb; - await createNews.save(); } /** @@ -956,6 +861,11 @@ module.exports.deletOneNews = [ await deleteNewsImage(targetNews, "cover"); await deleteNewsImage(targetNews, "thumbCover"); + for (const attachment of targetNews.attachments) { + const fileName = attachment.get("file", null, { getters: false }); + await deleteNewsAttachment(fileName); + } + await targetNews.deleteOne(); await Comment.deleteMany({ modelRef: targetNews._id }); @@ -970,13 +880,22 @@ module.exports.deletOneNews = [ module.exports.deleteAllNews = [ async (req, res) => { 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 }); return res.json("ok"); } catch (error) { - /** - * if we got unkown error show to client - */ return res500(res, error.message); } }, @@ -1271,52 +1190,65 @@ module.exports.searchForSlider = [ module.exports.addAttachmentToNews = [ [body("attTitle").notEmpty().withMessage(_sr["fa"].required.title)], checkValidations(validationResult), - (req, res) => { - if (!req.files?.file) - return res - .status(422) - .json({ validation: { file: { msg: _sr["fa"].required.file } } }); - if (req.files?.file.mimetype.split("/")[1] !== "pdf") - return res - .status(422) - .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } }); + async (req, res) => { + try { + if (!req.files?.file) { + return res + .status(422) + .json({ validation: { file: { msg: _sr["fa"].required.file } } }); + } - const data = { title: req.body.attTitle }; - const file = req.files.file; - const fileName = - req.body.attTitle.replace(titleFilterRegex, "-") + - "_" + - Date.now() + - ".pdf"; - file.mv(`./static/uploads/files/newsAttachments/${fileName}`); - data.file = fileName; + if (req.files.file.mimetype.split("/")[1] !== "pdf") { + return res + .status(422) + .json({ validation: { file: { msg: "فقط فرمت PDF قابل قبول است" } } }); + } - News.findById(req.params.newsId, (err, news) => { - if (err || !news) return res404(res, err); - news.attachments.push(data); - news.save((err) => { - if (err) return res500(res, err); - else return res.json({ message: _sr["fa"].response.success_save }); - }); - }); + const news = await News.findById(req.params.newsId); + if (!news) { + return res404(res, "خبر مورد نظر پیدا نشد"); + } + + const fileName = + req.body.attTitle.replace(titleFilterRegex, "-") + + "_" + + Date.now() + + ".pdf"; + + await uploadNewsAttachment(req.files.file.data, fileName); + + news.attachments.push({ title: req.body.attTitle, file: fileName }); + await news.save(); + + return res.json({ message: _sr["fa"].response.success_save }); + } catch (error) { + return res500(res, error.message); + } }, ]; module.exports.removeAttachmentFromNews = [ - (req, res) => { - News.findOne({ "attachments._id": req.params.fileId }, (err, news) => { - if (err || !news) return res404(res, err); - const file = news.attachments.id(req.params.fileId); - if (!file) return res404(res, "not found"); - file.remove(); - fs.unlink(`./static/${file.file}`, (err) => { - if (err) console.log(err); - }); - news.save((err) => { - if (err) return res500(res, err); - else return res.json({ message: _sr["fa"].response.success_remove }); - }); - }); + async (req, res) => { + try { + const news = await News.findOne({ "attachments._id": req.params.fileId }); + if (!news) { + return res404(res, "not found"); + } + + const attachment = news.attachments.id(req.params.fileId); + if (!attachment) { + return res404(res, "not found"); + } + + 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); + } }, ]; diff --git a/server/models/News.js b/server/models/News.js index a2b104e..4bc5110 100644 --- a/server/models/News.js +++ b/server/models/News.js @@ -8,12 +8,14 @@ function image(img) { return s3Storage.getPublicUrl(`uploads/images/news/${img}`); } -function file(file) { - return '/uploads/files/newsAttachments/' + file +function attachmentFile(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({ - file: {type: String, get: file}, + file: {type: String, get: attachmentFile}, title: String }) diff --git a/server/plugins/newsImageStorage.js b/server/plugins/newsImageStorage.js index 42f4963..7685705 100644 --- a/server/plugins/newsImageStorage.js +++ b/server/plugins/newsImageStorage.js @@ -2,6 +2,7 @@ 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 }); @@ -38,9 +39,36 @@ const uploadProcessedImage = async ( 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, };