const BlogCategory = require('../models/BlogCategory') const BlogPost = require('../models/BlogPost') const mongoose = require('mongoose') const sharp = require('sharp') const fs = require('fs') const {body, validationResult} = require('express-validator') const {res404, res500, checkValidations} = require('../plugins/controllersHelperFunctions') const {_sr} = require('../plugins/serverResponses') // variables const coverWidth = 1920 const coverHeight = 300 const coverQuality = 100 const coverThumbWidth = 400 const coverThumbHeight = 210 const coverThumbQuality = 100 const lastSectionImageWidth = 1920 const lastSectionImageHeight = 400 const lastSectionImageQuality = 100 const blogPaginateLimit = 10 const shortDescriptionLength = 250 const videoLimit = 150; module.exports.create = [ [ body('en_title') .notEmpty().withMessage(_sr['fa'].required.title) .bail() .custom((value, {req}) => { return BlogPost.findOne({$or: [{'locale.fa.title': value}, {'locale.en.title': value}]}) .then(post => { if (post) return Promise.reject(_sr['fa'].duplicated.title) else return true }) }), body('fa_title') .if((value, {req}) => value) .custom((value, {req}) => { return BlogPost.findOne({$or: [{'locale.fa.title': value}, {'locale.en.title': value}]}) .then(post => { if (post) return Promise.reject(_sr['fa'].duplicated.title) else return true }) }) ], checkValidations(validationResult), async (req, res) => { if (!req.files || !req.files.cover) return res.status(422).json({validation: {cover: {msg: _sr['fa'].required.cover}}}) if (!_sr.supportedImageFormats.includes(req.files.cover.mimetype.split('/')[1])) return res.status(422).json({validation: {cover: {msg: _sr['fa'].format.image}}}) const { fa_title, en_title } = req.body const data = { locale: { fa: { title: fa_title }, en: { title: en_title } } } const image = req.files.cover const imageName = 'blog_' + Date.now() + '.jpg' const imageThumb = 'thumb_' + imageName try { const target = await sharp(image.data) await target .resize(coverWidth, coverHeight, {fit: 'cover'}) .withMetadata() .toFormat('jpg') .jpeg({quality: coverQuality}) .toFile(`./static/uploads/images/blog/${imageName}`) await target .resize(coverThumbWidth, coverThumbHeight, {fit: 'cover'}) .withMetadata() .toFormat('jpg') .jpeg({quality: coverThumbQuality}) .toFile(`./static/uploads/images/blog/thumb/${imageThumb}`) data.cover = imageName data.thumb = imageThumb } catch (e) { return res500(res, e) } const post = new BlogPost(data) post.save((err, data) => { if (err) return res500(res, err) return res.json(data) }) } ] module.exports.getAll = [ async (req, res) => { ///////////// get available options // '/api/public/blogPosts' --> gets all published posts // '/api/public/blogPosts?getAll=true' --> gets all posts either published or not // '/api/public/blogPosts?limit=4' --> gets only 4 published post of all categories // '/api/public/blogPosts?category=${encodeURIComponent(categoryTitle)}&page=${pageNumber}' --> gets posts depend on the category title and in chunked data if (req.query.category) { try { const categoryID = await BlogCategory.findOne({$or: [{'locale.fa.title': req.query.category}, {'locale.en.title': req.query.category}]}) const filter = req.query.category === 'all' ? {published: true} : {published: true, category: categoryID._id} BlogPost.paginate(filter, {page: req.query.page, limit: blogPaginateLimit, populate: 'category', sort: {_id: -1}}, (err, posts) => { if (err) return res500(res, {message: _sr['fa'].not_found.item_id}) return res.json(posts) }) } catch (e) { return res500(res, e) } } else { let query = {published: true} if (req.query.getAll) delete query.published const limit = Number(req.query.limit) || false BlogPost.find(query).sort({_id: -1}).limit(limit).populate('category').exec((err, posts) => { if (err) return res500(res, err) else return res.json(posts) }) } } ] module.exports.getOne = [ (req, res) => { const query = req.params.id const isObjectId = mongoose.isValidObjectId(query) const noCategory = req.query.noCategory const filter = isObjectId ? {_id: query} : {$or: [{'locale.fa.title': query}, {'locale.en.title': query}]} BlogPost.findOne(filter).populate(noCategory ? '' : 'category').exec((err, post) => { if (err || !post) return res404(res, err) else return res.json(post) }) } ] module.exports.update = [ [ body('en_title') .notEmpty().withMessage(_sr['fa'].required.title) .bail() .custom((value, {req}) => { return BlogPost.findOne({$or: [{'locale.fa.title': value}, {'locale.en.title': value}]}) .then(post => { if (post && post._id.toString() !== req.params.id) return Promise.reject(_sr['fa'].duplicated.title) else return true }) }), body('fa_title') .if((value, {req}) => value) .custom((value, {req}) => { return BlogPost.findOne({$or: [{'locale.fa.title': value}, {'locale.en.title': value}]}) .then(post => { if (post && post._id.toString() !== req.params.id) return Promise.reject(_sr['fa'].duplicated.title) else return true }) }), body('category') .notEmpty().withMessage(_sr['fa'].required.category) .bail() .custom((value, {req}) => { if (mongoose.isValidObjectId(value)) { return BlogCategory.findById(value) .then(category => { if (!category) return Promise.reject(_sr['fa'].not_found.item_id) else return true }) } else return Promise.reject(_sr['fa'].required.category) }) ], checkValidations(validationResult), async (req, res) => { const { fa_title, en_title, fa_description, en_description, fa_short_description, en_short_description, fa_last_section_image_title, en_last_section_image_title, fa_last_section_image_description, en_last_section_image_description, fa_last_section_title, en_last_section_title, fa_last_section_description, en_last_section_description, category, published } = req.body const data = { locale: { fa: { title: fa_title || '', description: fa_description || '', short_description: fa_short_description.slice(0, shortDescriptionLength) || '' + ' ... ', last_section_image_title: fa_last_section_image_title || '', last_section_image_description: fa_last_section_image_description || '', last_section_title: fa_last_section_title || '', last_section_description: fa_last_section_description || '' }, en: { title: en_title || '', description: en_description || '', short_description: en_short_description.slice(0, shortDescriptionLength) || '' + ' ... ', last_section_image_title: en_last_section_image_title || '', last_section_image_description: en_last_section_image_description || '', last_section_title: en_last_section_title || '', last_section_description: en_last_section_description || '' } }, category, published } if (req.files) { if (req.files.cover) { if (!_sr.supportedImageFormats.includes(req.files.cover.mimetype.split('/')[1])) return res.status(422).json({validation: {cover: {msg: _sr['fa'].format.image}}}) const image = req.files.cover const imageName = 'blog_' + Date.now() + '.jpg' const imageThumb = 'thumb_' + imageName try { const target = await sharp(image.data) await target .resize(coverWidth, coverHeight, {fit: 'cover'}) .withMetadata() .withMetadata() .toFormat('jpg') .jpeg({quality: coverQuality}) .toFile(`./static/uploads/images/blog/${imageName}`) await target .resize(coverThumbWidth, coverThumbHeight, {fit: 'cover'}) .withMetadata() .withMetadata() .toFormat('jpg') .jpeg({quality: coverThumbQuality}) .toFile(`./static/uploads/images/blog/thumb/${imageThumb}`) data.cover = imageName data.thumb = imageThumb } catch (e) { return res500(res, e) } } if (req.files.last_section_image) { if (!_sr.supportedImageFormats.includes(req.files.last_section_image.mimetype.split('/')[1])) return res.status(422).json({validation: {cover: {msg: _sr['fa'].format.image}}}) const image = req.files.last_section_image const imageName = 'blog_' + Date.now() + '.jpg' const imageThumb = 'thumb_' + imageName try { const target = await sharp(image.data) await target .resize(lastSectionImageWidth, lastSectionImageHeight, {fit: 'cover'}) .withMetadata() .toFormat('jpg') .jpeg({quality: lastSectionImageQuality}) .toFile(`./static/uploads/images/blog/${imageName}`) data.last_section_image = imageName } catch (e) { return res500(res, e) } } if (req.files.video) { const file = req?.files?.video if (file.mimetype.split('/')[1] !== 'mp4') return res.status(422).json({validation: {video: {msg: _sr['fa'].format.video}}}) ///// start convert byte to mb const mb = file.size / (1024 * 1024); ///// check limit size of video if (mb > videoLimit) { return res.status(422).json({ validation: {file: {msg: `${_sr['fa'].max_char.data_size} 150 مگابایت`}} }); } const videoName = 'blog_' + Date.now() + '.mp4' const uploadPath = `./static/uploads/videos/blog/${videoName}`; try { await file.mv(uploadPath); data.video = videoName } catch (e) { return res500(res, e) } } } BlogPost.findByIdAndUpdate(req.params.id, data, (err, oldData) => { if (err || !oldData) return res404(res, err) if (req.files && req.files.cover) { fs.unlink(`./static/${oldData.cover}`, (err) => { if (err) console.log(err) }) fs.unlink(`./static/${oldData.thumb}`, (err) => { if (err) console.log(err) }) } if (req.files && req.files.last_section_image && oldData.last_section_image) { fs.unlink(`./static/${oldData.last_section_image}`, (err) => { if (err) console.log(err) }) } if (req.files && req.files.video && oldData.video) { fs.unlink(`./static/${oldData.video}`, (err) => { if (err) console.log(err) }) } return res.json({message: _sr['fa'].response.success_save}) }) } ] module.exports.delete = [ (req, res) => { BlogPost.findByIdAndRemove(req.params.id, (err, data) => { if (err || !data) return res404(res, err) fs.unlink(`./static/${data.cover}`, err => { if (err) console.log(err) }) fs.unlink(`./static/${data.thumb}`, err => { if (err) console.log(err) }) if (data.last_section_image) fs.unlink(`./static/${data.last_section_image}`, err => { if (err) console.log(err) }) if (data.video) fs.unlink(`./static/${data.video}`, err => { if (err) console.log(err) }) return res.json({message: _sr['fa'].response.success_remove}) }) } ]