Merge pull request #20 from fatchan/batch-action-changes

action handler improvements, omitted posts patch refer #17 #18
merge-requests/208/head
Tom 5 years ago committed by GitHub
commit a20f3bc639
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 144
      controllers/forms.js
  2. 24
      db/posts.js
  3. 48
      helpers/actionchecker.js
  4. 16
      models/forms/ban-poster.js
  5. 26
      models/forms/delete-post.js
  6. 33
      models/forms/deletepostsfiles.js
  7. 11
      models/forms/dismiss-report.js
  8. 11
      models/forms/dismissglobalreport.js
  9. 38
      models/forms/spoiler-post.js
  10. 2
      views/mixins/post.pug
  11. 2
      views/pages/message.pug

@ -21,9 +21,13 @@ const express = require('express')
, loginAccount = require(__dirname+'/../models/forms/login.js')
, changePassword = require(__dirname+'/../models/forms/changepassword.js')
, registerAccount = require(__dirname+'/../models/forms/register.js')
, hasPerms = require(__dirname+'/../helpers/haspermsmiddleware.js')
, checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js')
, checkPerms = require(__dirname+'/../helpers/hasperms.js')
, numberConverter = require(__dirname+'/../helpers/number-converter.js')
, banCheck = require(__dirname+'/../helpers/bancheck.js');
, banCheck = require(__dirname+'/../helpers/bancheck.js')
, actionChecker = require(__dirname+'/../helpers/actionchecker.js')
, actions = ['report', 'global_report', 'spoiler', 'delete', 'delete_file', 'dismiss', 'global_dismiss', 'ban', 'global_ban']
, authActions = ['dismiss', 'global_dismiss', 'ban', 'global_ban'];
// login to account
router.post('/login', (req, res, next) => {
@ -202,7 +206,7 @@ router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, asyn
});
//upload banners
router.post('/board/:board/addbanners', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => {
router.post('/board/:board/addbanners', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@ -238,7 +242,7 @@ router.post('/board/:board/addbanners', Boards.exists, banCheck, hasPerms, numbe
});
//delete banners
router.post('/board/:board/deletebanners', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => {
router.post('/board/:board/deletebanners', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => {
const errors = [];
@ -278,9 +282,25 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a
const errors = [];
//make sure they checked 1-10 posts
if (!req.body.checkedposts || req.body.checkedposts.length === 0 || req.body.checkedposts.length > 10) {
errors.push('Must select 1-10 posts')
}
//get what type of actions
const { anyPasswords, anyAuthed, anyValid } = actionChecker(req);
//make sure they selected at least 1 action
if (!anyValid) {
errors.push('No actions selected')
}
//check if they have permission to perform the actions
const hasPerms = checkPerms(req, res);
if(!hasPerms && anyAuthed) {
errors.push('No permission')
}
//check that actions are valid
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
@ -290,17 +310,6 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 characters or less');
}
if (!(req.body.report
|| req.body.global_report
|| req.body.spoiler
|| req.body.delete
|| req.body.delete_file
|| req.body.dismiss
|| req.body.global_dismiss
|| req.body.ban
|| req.body.global_ban)) {
errors.push('Invalid actions selected')
}
if (req.body.report && (!req.body.report_reason || req.body.report_reason.length === 0)) {
errors.push('Reports must have a reason')
}
@ -317,53 +326,61 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a
if (!posts || posts.length === 0) {
return res.status(404).render('message', {
'title': 'Not found',
'errors': 'Selected posts not found',
'error': 'Selected posts not found',
'redirect': `/${req.params.board}`
})
}
let passwordPosts = posts;
if (!hasPerms && anyPasswords) {
//if any actions require passwords, filter to ones with correct password
passwordPosts = posts.filter(post => {
return post.password != null
&& post.password.length > 0
&& post.password == req.body.password
});
if (passwordPosts.length === 0) {
return res.status(403).render('message', {
'title': 'Forbidden',
'error': 'Password did not match any selected posts',
'redirect': `/${req.params.board}`
});
}
}
const messages = [];
try {
// if getting global banned, board ban doesnt matter
if (req.body.global_ban) {
messages.push((await banPoster(req, res, next, null, posts)));
} else if (req.body.ban) {
messages.push((await banPoster(req, res, next, req.params.board, posts)));
if (hasPerms) {
// if getting global banned, board ban doesnt matter
if (req.body.global_ban) {
messages.push((await banPoster(req, res, next, null, posts)));
} else if (req.body.ban) {
messages.push((await banPoster(req, res, next, req.params.board, posts)));
}
}
//ban before deleting
if (req.body.delete) {
messages.push((await deletePosts(req, res, next, posts)));
messages.push((await deletePosts(req, res, next, passwordPosts)));
} else {
// if it was getting deleted, we cant do any of these
if (req.body.delete_file) {
messages.push((await deletePostsFiles(req, res, next, posts)));
} if (req.body.spoiler) {
messages.push((await spoilerPosts(req, res, next, posts)));
messages.push((await deletePostsFiles(req, res, next, passwordPosts)));
} else if (req.body.spoiler) {
messages.push((await spoilerPosts(req, res, next, passwordPosts)));
}
// cannot report and dismiss at same time
if (req.body.report) {
messages.push((await reportPosts(req, res, next)));
} else if (req.body.dismiss) {
} else if (hasPerms && req.body.dismiss) {
messages.push((await dismissReports(req, res, next)));
}
// cannot report and dismiss at same time
if (req.body.global_report) {
messages.push((await globalReportPosts(req, res, next, posts)));
} else if (req.body.global_dismiss) {
} else if (hasPerms && req.body.global_dismiss) {
messages.push((await dismissGlobalReports(req, res, next, posts)));
}
}
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}
@ -376,7 +393,7 @@ router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, a
});
//unban
router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => {
router.post('/board/:board/unban', Boards.exists, banCheck, checkPermsMiddleware, numberConverter, async (req, res, next) => {
//keep this for later in case i add other options to unbans
const errors = [];
@ -397,12 +414,6 @@ router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConv
try {
messages.push((await removeBans(req, res, next)));
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}
@ -414,32 +425,46 @@ router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConv
});
router.post('/global/actions', hasPerms, numberConverter, async(req, res, next) => {
router.post('/global/actions', checkPermsMiddleware, numberConverter, async(req, res, next) => {
const errors = [];
//make sure they checked 1-10 posts
if (!req.body.globalcheckedposts || req.body.globalcheckedposts.length === 0 || req.body.globalcheckedposts.length > 10) {
errors.push('Must select 1-10 posts')
}
const { anyGlobal } = actionChecker(req);
//make sure they selected at least 1 global action
if (!anyGlobal) {
errors.push('Invalid actions selected');
}
//check that actions are valid
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (req.body.report_reason && req.body.report_reason.length > 50) {
errors.push('Report must be 50 characters or less');
}
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 characters or less');
}
if (!(req.body.spoiler
|| req.body.delete
|| req.body.delete_file
|| req.body.global_dismiss
|| req.body.global_ban)) {
errors.push('Invalid actions selected')
if (req.body.report && (!req.body.report_reason || req.body.report_reason.length === 0)) {
errors.push('Reports must have a reason')
}
//return the errors
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/globalmanage'
'redirect': `/${req.params.board}`
})
}
//get posts with global ids only
const posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true);
if (!posts || posts.length === 0) {
return res.status(404).render('message', {
@ -451,12 +476,10 @@ router.post('/global/actions', hasPerms, numberConverter, async(req, res, next)
const messages = [];
try {
//ban before delete
if (req.body.global_ban) {
messages.push((await banPoster(req, res, next, null, posts)));
}
// if its getting deleted, we cant do anythign else
if (req.body.delete) {
messages.push((await deletePosts(req, res, next, posts)));
@ -471,9 +494,7 @@ router.post('/global/actions', hasPerms, numberConverter, async(req, res, next)
messages.push((await dismissGlobalReports(req, res, next, posts)));
}
}
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
@ -490,9 +511,8 @@ router.post('/global/actions', hasPerms, numberConverter, async(req, res, next)
});
router.post('/global/unban', hasPerms, numberConverter, async(req, res, next) => {
router.post('/global/unban', checkPermsMiddleware, numberConverter, async(req, res, next) => {
//TODO
const errors = [];
if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) {
@ -511,12 +531,6 @@ router.post('/global/unban', hasPerms, numberConverter, async(req, res, next) =>
try {
messages.push((await removeBans(req, res, next)));
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}

@ -46,13 +46,15 @@ module.exports = {
//reverse order for board page
thread.replies = replies.reverse();
//count omitted image and posts
const numPreviewImages = replies.reduce((acc, post) => {
return acc + post.files.length;
}, 0);
thread.omittedimages = thread.replyimages - numPreviewImages;
thread.omittedposts = thread.replyposts - replies.length;
//temporary mitigation for deletion issue
if (replies.length >= 5) {
//count omitted image and posts
const numPreviewImages = replies.reduce((acc, post) => {
return acc + post.files.length;
}, 0);
thread.omittedimages = thread.replyimages - numPreviewImages;
thread.omittedposts = thread.replyposts - replies.length;
}
}));
return threads;
@ -262,6 +264,13 @@ module.exports = {
'$exists': true
},
'board': board
}, {
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
'globalreports': 0,
}
}).toArray();
},
@ -289,7 +298,6 @@ module.exports = {
'password': 0,
'ip': 0,
'reports': 0,
'globalreports': 0,
}
}).toArray();
},

@ -0,0 +1,48 @@
'use strict';
const actions = [
{name:'lock', global:false, auth:true, passwords:false},
{name:'sticky', global:false, auth:true, passwords:false},
{name:'report', global:false, auth:false, passwords:false},
{name:'global_report', global:false, auth:false, passwords:false},
{name:'spoiler', global:true, auth:false, passwords:true},
{name:'delete', global:true, auth:false, passwords:true},
{name:'delete_file', global:true, auth:false, passwords:true},
{name:'dismiss', global:false, auth:true, passwords:false},
{name:'global_dismiss', global:true, auth:true, passwords:false},
{name:'ban', global:false, auth:true, passwords:false},
{name:'global_ban', global:true, auth:true, passwords:false},
];
module.exports = (req, res) => {
let anyGlobal = false
, anyAuthed = false
, anyPasswords = false
, anyValid = false;
for (let i = 0; i < actions.length; i++) {
const action = actions[i];
const bodyHasAction = req.body[action.name];
if (bodyHasAction) {
if (!anyGlobal && action.global) {
anyGlobal = true;
}
if (!anyAuthed && action.auth) {
anyAuthed = true;
}
if (!anyPasswords && action.passwords) {
anyPasswords = true;
}
if (!anyValid) {
anyValid = true;
}
}
if (anyGlobal && anyAuthed && anyValid) {
break;
}
}
return { anyGlobal, anyAuthed, anyValid, anyPasswords };
}

@ -5,21 +5,7 @@ const uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, Bans = require(__dirname+'/../../db/bans.js')
, Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next, board, checkedPosts) => {
const posts = checkedPosts;
//if user is not logged in or if logged in but not authed, they cannot ban
if (!hasPerms(req, res)) {
throw {
'status': 403,
'message': {
'title': 'Forbidden',
'message': 'You do not have permission to issue bans',
'redirect': `/${req.params.board}`
}
};
}
module.exports = async (req, res, next, board, posts) => {
const bans = posts.map(post => {
return {

@ -5,34 +5,10 @@ const path = require('path')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, hasPerms = require(__dirname+'/../../helpers/hasperms.js')
, Mongo = require(__dirname+'/../../db/db.js')
, Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next, checkedPosts) => {
let posts = checkedPosts;
//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
posts = posts.filter(post => {
// only include posts that have a password and that matches
return post.password != null
&& post.password.length > 0
&& post.password == req.body.password
});
if (posts.length === 0) {
throw {
'status': 403,
'message': {
'title': 'Forbidden',
'message': 'Password did not match any selected posts',
'redirect': `/${req.params.board}`
}
};
}
}
module.exports = async (req, res, next, posts) => {
//filter to threads, then get the board and thread for each
const boardThreads = posts.filter(x => x.thread == null).map(x => {

@ -9,34 +9,7 @@ const path = require('path')
, Mongo = require(__dirname+'/../../db/db.js')
, Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next, checkedPosts) => {
let posts = checkedPosts;
//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
posts = posts.filter(post => {
// only include posts that have a password and that matches
return post.password != null
&& post.password.length > 0
&& post.password == req.body.password
});
if (posts.length === 0) {
throw {
'status': 403,
'message': {
'title': 'Forbidden',
'message': 'Password did not match any selected posts',
'redirect': `/${req.params.board}`
}
};
}
}
//delete posts from DB
const postMongoIds = posts.map(post => Mongo.ObjectId(post._id))
const deletedFilesPosts = await Posts.deleteFilesMany(postMongoIds).then(result => result.deletedCount);
module.exports = async (req, res, next, posts) => {
//get filenames from all the posts
let fileNames = [];
@ -53,6 +26,10 @@ module.exports = async (req, res, next, checkedPosts) => {
])
}));
//delete posts from DB
const postMongoIds = posts.map(post => Mongo.ObjectId(post._id))
const deletedFilesPosts = await Posts.deleteFilesMany(postMongoIds).then(result => result.deletedCount);
//hooray!
return `Deleted ${fileNames.length} files across ${deletedFilesPosts} posts`

@ -5,17 +5,6 @@ const Posts = require(__dirname+'/../../db/posts.js')
module.exports = async (req, res, next) => {
if (!hasPerms(req, res)) {
throw {
'status': 403,
'message': {
'title': 'Forbidden',
'message': `You are not authorised to dismiss reports.`,
'redirect': `/${req.params.board}`
}
};
}
const dismissedReports = await Posts.dismissReports(req.params.board, req.body.checkedposts).then(result => result.modifiedCount);
return `Dismissed ${dismissedReports} reports successfully`;

@ -6,17 +6,6 @@ const Mongo = require(__dirname+'/../../db/db.js')
module.exports = async (req, res, next, posts) => {
if (!hasPerms(req, res)) {
throw {
'status': 403,
'message': {
'title': 'Forbidden',
'message': 'You are not authorised to dismiss global reports.',
'redirect': '/'
}
};
}
const postMongoIds = posts.map(post => Mongo.ObjectId(post._id))
const dismissedCount = await Posts.dismissGlobalReports(postMongoIds).then(result => result.modifiedCount);

@ -9,46 +9,14 @@ const path = require('path')
, Mongo = require(__dirname+'/../../db/db.js')
, Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next, checkedPosts) => {
module.exports = async (req, res, next, posts) => {
//get all posts that were checked
let posts = checkedPosts;
//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) {
throw {
'status': 403,
'message': {
'title': 'Forbidden',
'message': 'Password did not match any selected posts',
'redirect': '/'
}
};
}
}
//filter by not spoilered. maybe i add filters with optiins in the controller where it gets the posts?
// filter to ones not spoilered
posts = posts.filter(post => {
return !post.spoiler
});
if (posts.length === 0) {
throw {
'status': 409,
'message': {
'title': 'Conflict',
'message': 'Posts already spoilered',
'redirect': '/'
}
};
return 'Posts already spoilered';
}
// spoiler posts

@ -44,7 +44,7 @@ mixin post(post, truncate, manage=false, globalmanage=false)
let truncatedMessage = post.mesage;
let truncated = false;
if (messageLines > 10 || post.message.length > 1000) {
truncatedMessage = splitPost.slice(0, 16).join('\n');
truncatedMessage = splitPost.slice(0, 10).join('\n');
truncated = true;
}
if truncated

@ -8,6 +8,8 @@ block content
ul
if message
li #{message}
if error
li #{error}
if messages
each msg in messages
li #{msg}

Loading…
Cancel
Save