diff --git a/controllers/forms.js b/controllers/forms.js index d53e8883..677d87bf 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -7,6 +7,7 @@ const express = require('express') , Trips = require(__dirname+'/../db-models/trips.js') , makePost = require(__dirname+'/../models/forms/make-post.js') , deletePosts = require(__dirname+'/../models/forms/delete-post.js') + , spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js') , reportPosts = require(__dirname+'/../models/forms/report-post.js') , dismissReports = require(__dirname+'/../models/forms/dismiss-report.js') , loginAccount = require(__dirname+'/../models/forms/login.js') @@ -151,7 +152,7 @@ router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, ne if (req.body.reason && req.body.reason.length > 50) { errors.push('Report must be 50 characters or less'); } - if (!(req.body.report || req.body.delete || req.body.dismiss)) { + if (!(req.body.report || req.body.delete || req.body.dismiss || req.body.spoiler)) { errors.push('Must select an action') } if (req.body.report && (!req.body.reason || req.body.reason.length === 0)) { @@ -170,7 +171,9 @@ router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, ne reportPosts(req, res); } else if (req.body.delete) { deletePosts(req, res); - } else { + } else if (req.body.spoiler) { + spoilerPosts(req, res); + } else if (req.body.dismiss) { dismissReports(req, res); } diff --git a/db-models/posts.js b/db-models/posts.js index b3577354..0688a6ce 100644 --- a/db-models/posts.js +++ b/db-models/posts.js @@ -255,6 +255,21 @@ module.exports = { }, + spoilerMany: (board, ids) => { + + return db.updateMany({ + 'postId': { + '$in': ids + }, + 'board': board + }, { + '$set': { + 'spoiler': true + } + }); + + }, + deleteAll: (board) => { return db.deleteMany({ 'board': board diff --git a/models/forms/delete-post.js b/models/forms/delete-post.js index c35ebdde..20d63536 100644 --- a/models/forms/delete-post.js +++ b/models/forms/delete-post.js @@ -6,7 +6,7 @@ const path = require('path') , unlink = util.promisify(fs.unlink) , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , hasPerms = require(__dirname+'/../../helpers/has-perms.js') - , Posts = require(__dirname+'/../../db-models/posts.js'); + , Posts = require(__dirname+'/../../db-models/posts.js'); module.exports = async (req, res) => { @@ -19,6 +19,14 @@ module.exports = async (req, res) => { return res.status(500).render('error'); } + if (!posts || posts.length === 0) { + return res.status(400).render('message', { + 'title': 'Bad requests', + 'message': 'No posts found', + 'redirect': `/${req.params.board}` + }); + } + //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null if (!hasPerms(req, res)) { // filter posts by password only if NOT board moderator or owner @@ -28,60 +36,57 @@ module.exports = async (req, res) => { && post.password.length > 0 && post.password == req.body.password }); + if (posts.length === 0) { + return res.status(403).render('message', { + 'title': 'Forbidden', + 'message': 'Password did not match any selected posts', + 'redirect': `/${req.params.board}` + }); + } } - if (posts.length > 0) { + const threadIds = posts.filter(x => x.thread == null).map(x => x.postId); - const threadIds = posts.filter(x => x.thread == null).map(x => x.postId); - - //get posts from all threads - let threadPosts = [] - await Promise.all(threadIds.map(async id => { - const currentThreadPosts = await Posts.getThreadPosts(req.params.board, id); - threadPosts = threadPosts.concat(currentThreadPosts); - return; - })) - - //combine them all into one array - const allPosts = posts.concat(threadPosts) - - //delete posts from DB - let deletedPosts = 0; - try { - const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId)); - deletedPosts = result.deletedCount; - } catch (err) { - console.error(err); - return res.status(500).render('error'); - } + //get posts from all threads + let threadPosts = [] + await Promise.all(threadIds.map(async id => { + const currentThreadPosts = await Posts.getThreadPosts(req.params.board, id); + threadPosts = threadPosts.concat(currentThreadPosts); + return; + })) - //get filenames from all the posts - let fileNames = []; - allPosts.forEach(post => { - fileNames = fileNames.concat(post.files.map(x => x.filename)) - }) + //combine them all into one array + const allPosts = posts.concat(threadPosts) - //delete all the files using the filenames - await Promise.all(fileNames.map(async filename => { - //dont question it. - return Promise.all([ - unlink(uploadDirectory + filename), - unlink(uploadDirectory + 'thumb-' + filename) - ]) - })); + //delete posts from DB + let deletedPosts = 0; + try { + const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId)); + deletedPosts = result.deletedCount; + } catch (err) { + console.error(err); + return res.status(500).render('error'); + } - //hooray! - return res.render('message', { - 'title': 'Success', - 'message': `Deleted ${threadIds.length} threads and ${deletedPosts} posts`, - 'redirect': `/${req.params.board}` - }); + //get filenames from all the posts + let fileNames = []; + allPosts.forEach(post => { + fileNames = fileNames.concat(post.files.map(x => x.filename)) + }) - } + //delete all the files using the filenames + await Promise.all(fileNames.map(async filename => { + //dont question it. + return Promise.all([ + unlink(uploadDirectory + filename), + unlink(uploadDirectory + 'thumb-' + filename) + ]) + })); - return res.status(403).render('message', { - 'title': 'Forbidden', - 'message': 'Password did not match any selected posts', + //hooray! + return res.render('message', { + 'title': 'Success', + 'message': `Deleted ${threadIds.length} threads and ${deletedPosts} posts`, 'redirect': `/${req.params.board}` }); diff --git a/models/forms/spoiler-post.js b/models/forms/spoiler-post.js new file mode 100644 index 00000000..15847f77 --- /dev/null +++ b/models/forms/spoiler-post.js @@ -0,0 +1,78 @@ +'use strict'; + +const path = require('path') + , util = require('util') + , fs = require('fs') + , unlink = util.promisify(fs.unlink) + , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') + , hasPerms = require(__dirname+'/../../helpers/has-perms.js') + , Posts = require(__dirname+'/../../db-models/posts.js'); + +module.exports = async (req, res) => { + + //get all posts that were checked + let posts; + try { + posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts + } catch (err) { + console.error(err); + return res.status(500).render('error'); + } + + if (!posts || posts.length === 0) { + return res.status(400).render('message', { + 'title': 'Bad requests', + 'message': 'No posts found', + 'redirect': `/${req.params.board}` + }); + } + + //if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null + if (!hasPerms(req, res)) { + + //filter by password + posts = posts.filter(post => { + return post.password != null + && post.password.length > 0 + && post.password == req.body.password + }); + if (posts.length === 0) { + return res.status(403).render('message', { + 'title': 'Forbidden', + 'message': 'Password did not match any selected posts', + 'redirect': `/${req.params.board}` + }); + } + + } + + //filter by not spoilered + posts = posts.filter(post => { + return !post.spoiler + }); + if (posts.length === 0) { + return res.status(409).render('message', { + 'title': 'Conflict', + 'message': 'Selected posts are already spoilered', + 'redirect': `/${req.params.board}` + }); + } + + // spoiler posts + let spoileredPosts = 0; + try { + const result = await Posts.spoilerMany(req.params.board, posts.map(x => x.postId)); + spoileredPosts = result.modifiedCount; + } catch (err) { + console.error(err); + return res.status(500).render('error'); + } + + //hooray! + return res.render('message', { + 'title': 'Success', + 'message': `Spoilered ${spoileredPosts} posts`, + 'redirect': `/${req.params.board}` + }); + +} diff --git a/views/includes/deletefooter.pug b/views/includes/deletefooter.pug index dee52503..7f74c19f 100644 --- a/views/includes/deletefooter.pug +++ b/views/includes/deletefooter.pug @@ -3,10 +3,13 @@ section.action-wrapper label input.post-check(type='checkbox', name='delete' value=1) | Delete - input#password(type='password', name='password', placeholder='deletion password' autocomplete='off') + label + input.post-check(type='checkbox', name='spoiler' value=1) + | Spoiler + input#password(type='password', name='password', placeholder='post password' autocomplete='off') span label input.post-check(type='checkbox', name='report' value=1) - | Report - input#report(type='text', name='reason', placeholder='report reason' autocomplete='off') + | Report + input#report(type='text', name='reason', placeholder='reason' autocomplete='off') input(type='submit', value='submit') diff --git a/views/includes/postform.pug b/views/includes/postform.pug index af9759b2..d304b6a1 100644 --- a/views/includes/postform.pug +++ b/views/includes/postform.pug @@ -11,7 +11,7 @@ section.form-wrapper input#name(type='text', name='email', placeholder='email' autocomplete='off' maxlength='50') - input#password(type='password', name='password', placeholder='password (for deletion)' autocomplete='off' maxlength='50') + input#password(type='password', name='password', placeholder='post password' autocomplete='off' maxlength='50') textarea#message(name='message', rows='8', cols='50', placeholder='message' autocomplete='off' maxlength='2000') diff --git a/views/pages/manage.pug b/views/pages/manage.pug index 190c8d70..a99461b1 100644 --- a/views/pages/manage.pug +++ b/views/pages/manage.pug @@ -22,6 +22,10 @@ block content label input.post-check(type='checkbox', name='delete' value=1) | Delete + span + label + input.post-check(type='checkbox', name='spoiler' value=1) + | Spoiler span label input.post-check(type='checkbox', name='dismiss' value=1)