ability to delete own board for BO/global/admin

merge-requests/208/head
fatchan 5 years ago
parent ac55522492
commit 70b38d96e7
  1. 45
      controllers/forms.js
  2. 6
      db/bans.js
  3. 4
      db/boards.js
  4. 6
      db/posts.js
  5. 15
      gulpfile.js
  6. 2
      models/forms/create.js
  7. 138
      models/forms/deletepost.js
  8. 2
      models/forms/makepost.js
  9. 13
      views/pages/manage.pug

@ -5,6 +5,7 @@ const express = require('express')
, { enableUserBoards } = require(__dirname+'/../configs/main.json')
, Boards = require(__dirname+'/../db/boards.js')
, Posts = require(__dirname+'/../db/posts.js')
, Bans = require(__dirname+'/../db/bans.js')
, Mongo = require(__dirname+'/../db/db.js')
, { remove } = require('fs-extra')
, upload = require('express-fileupload')
@ -36,6 +37,7 @@ const express = require('express')
})
, removeBans = require(__dirname+'/../models/forms/removebans.js')
, makePost = require(__dirname+'/../models/forms/makepost.js')
, deletePosts = require(__dirname+'/../models/forms/deletepost.js')
, deleteTempFiles = require(__dirname+'/../helpers/files/deletetempfiles.js')
, uploadBanners = require(__dirname+'/../models/forms/uploadbanners.js')
, deleteBanners = require(__dirname+'/../models/forms/deletebanners.js')
@ -53,6 +55,7 @@ const express = require('express')
, verifyCaptcha = require(__dirname+'/../helpers/captcha/captchaverify.js')
, actionHandler = require(__dirname+'/../models/forms/actionhandler.js')
, csrf = require(__dirname+'/../helpers/checks/csrfmiddleware.js')
, uploadDirectory = require(__dirname+'/../helpers/files/uploadDirectory.js')
, actionChecker = require(__dirname+'/../helpers/checks/actionchecker.js');
@ -628,6 +631,48 @@ router.post('/board/:board/unban', csrf, Boards.exists, banCheck, isLoggedIn, ch
});
//delete board
router.post('/board/:board/deleteboard', csrf, Boards.exists, banCheck, isLoggedIn, checkPermsMiddleware(2), async (req, res, next) => {
const errors = [];
if (!req.body.confirm) {
errors.push('Missing confirmation');
}
if (!req.body.uri | req.body.uri !== req.params.board) {
errors.push('URI does not match')
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage.html`
});
}
try {
//todo: move this to separate model file
// could be slow, might also wanna use projection to just get the files and other info necessary for deleteposts model
await Boards.deleteOne(res.locals.board._id);
const allPosts = await Posts.allBoardPosts(res.locals.board._id);
if (allPosts.length > 0) {
await deletePosts(allPosts, res.locals.board._id, true);
}
await Bans.deleteBoard(res.locals.board._id);
await remove(`${uploadDirectory}html/${req.params.board}/`)
} catch (err) {
return next(err);
}
return res.render('message', {
'title': 'Success',
'message': 'Board deleted',
'redirect': '/'
});
});
router.post('/global/unban', csrf, isLoggedIn, checkPermsMiddleware(1), paramConverter, async(req, res, next) => {
const errors = [];

@ -51,6 +51,12 @@ module.exports = {
})
},
deleteBoard: (board) => {
return db.deleteMany({
'board': board
});
},
insertOne: (ban) => {
return db.insertOne(ban);
},

@ -19,6 +19,10 @@ module.exports = {
return db.collection('boards').insertOne(data);
},
deleteOne: (board) => {
return db.collection('boards').deleteOne({ '_id': board });
},
deleteAll: (board) => {
return db.collection('boards').deleteMany({});
},

@ -228,6 +228,12 @@ module.exports = {
},
allBoardPosts: (board) => {
return db.find({
'board': board
}).toArray();
},
//takes array "ids" of post ids
getPosts: (board, ids, admin) => {

@ -53,7 +53,7 @@ async function wipe() {
'name': 'test',
'description': 'testing board',
'captchaMode': 0,
'locked': true,
'locked': false,
'tphTrigger': 10,
'tphTriggerAction': 2,
'forceAnon': true,
@ -73,7 +73,7 @@ async function wipe() {
'raw':null,
'markdown':null
},
'filters':[]
'filters':[],
'filterMode': 0,
}
})
@ -119,15 +119,17 @@ function images() {
.pipe(gulp.dest(paths.images.dest));
}
//TODO: pages here that users should edit built and output by pug e.g. homepage, FAQ, contact, privacy policy, tos, etc
async function html() {
await del([ 'static/html/*' ]); //these will be now build-on-load
function html() {
return del([ 'static/html/*' ]); //these will be now build-on-load
}
function custompages() {
return gulp.src(paths.pug.src)
.pipe(pug())
.pipe(gulp.dest(paths.pug.dest));
}
const build = gulp.parallel(css, images, html);
const build = gulp.parallel(css, images, html, custompages);
const reset = gulp.series(wipe, build)
module.exports = {
@ -135,5 +137,6 @@ module.exports = {
css,
images,
reset,
custompages,
default: build,
};

@ -33,7 +33,7 @@ module.exports = async (req, res, next) => {
'settings': {
name,
description,
'locked': true,
'locked': false,
'captchaMode': 0,
'tphTrigger': 0,
'tphTriggerAction': 0,

@ -16,21 +16,21 @@ const uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.
}
}
module.exports = async (posts, board) => {
module.exports = async (posts, board, all=false) => {
//filter to threads
const threads = posts.filter(x => x.thread == null);
//delete the html for threads
const deleteHTML = []
for (let i = 0; i < threads.length; i++) {
deleteHTML.push(remove(`${uploadDirectory}html/${threads[i].board}/thread/${threads[i].postId}.html`));
if (threads.length > 0) {
//delete the html for threads
await Promise.all(threads.map(thread => {
remove(`${uploadDirectory}html/${threads.board}/thread/${threads.postId}.html`)
}));
}
await Promise.all(deleteHTML);
//get posts from all threads
let threadPosts = []
if (threads.length > 0) {
if (all === false && threads.length > 0) {
if (board) {
//if this is board-specific, we can use a single query
const threadPostIds = threads.map(thread => thread.postId);
@ -66,81 +66,83 @@ module.exports = async (posts, board) => {
await Files.decrement(fileNames);
}
//use this to not do unnecessary actions for posts where the thread is being deleted
const deleteThreadMap = {};
for (let i = 0; i < threads.length; i++) {
const thread = threads[i];
//if exists, add to set, else make the set
if (!deleteThreadMap[thread.board]) {
deleteThreadMap[thread.board] = new Set();
const bulkWrites = [];
if (all === false) { //no need to rebuild quotes when deleting all posts for a board
const deleteThreadMap = {};
for (let i = 0; i < threads.length; i++) {
const thread = threads[i];
//if exists, add to set, else make the set
if (!deleteThreadMap[thread.board]) {
deleteThreadMap[thread.board] = new Set();
}
deleteThreadMap[thread.board].add(thread.postId);
}
deleteThreadMap[thread.board].add(thread.postId);
}
const bulkWrites = [];
const backlinkRebuilds = new Set();
for (let j = 0; j < allPosts.length; j++) {
const post = allPosts[j];
backlinkRebuilds.delete(post._id); //make sure we dont try and remarkup this post since its getting deleted.
if (post.thread != null && !deleteThreadMap[post.board] || !deleteThreadMap[post.board].has(post.thread)) {
//get backlinks for posts to remarkup
for (let i = 0; i < post.backlinks.length; i++) {
const backlink = post.backlinks[i];
if (!backlinkRebuilds.has(backlink._id)) {
backlinkRebuilds.add(backlink._id);
const backlinkRebuilds = new Set();
for (let j = 0; j < allPosts.length; j++) {
const post = allPosts[j];
backlinkRebuilds.delete(post._id); //make sure we dont try and remarkup this post since its getting deleted.
if (post.thread != null && !deleteThreadMap[post.board] || !deleteThreadMap[post.board].has(post.thread)) {
//get backlinks for posts to remarkup
for (let i = 0; i < post.backlinks.length; i++) {
const backlink = post.backlinks[i];
if (!backlinkRebuilds.has(backlink._id)) {
backlinkRebuilds.add(backlink._id);
}
}
}
//remove dead backlinks to this post
if (post.quotes.length > 0) {
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': post.quotes.map(q => q._id)
}
},
'update': {
'$pull': {
'backlinks': {
'postId': post.postId
//remove dead backlinks to this post
if (post.quotes.length > 0) {
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': post.quotes.map(q => q._id)
}
},
'update': {
'$pull': {
'backlinks': {
'postId': post.postId
}
}
}
}
}
});
});
}
}
}
}
//deleting before remarkup so quotes are accurate
const deletedPosts = await Posts.deleteMany(postMongoIds).then(result => result.deletedCount);
//get posts that quoted deleted posts so we can remarkup them
if (backlinkRebuilds.size > 0) {
const remarkupPosts = await Posts.globalGetPosts([...backlinkRebuilds]);
await Promise.all(remarkupPosts.map(async post => { //doing these all at once
if (post.nomarkup && post.nomarkup.length > 0) { //is this check even necessary? how would it have a quote with no message
//redo the markup
let message = simpleMarkdown(post.nomarkup);
const { quotedMessage, threadQuotes } = await linkQuotes(post.board, post.nomarkup, post.thread);
message = sanitize(quotedMessage, sanitizeOptions);
bulkWrites.push({
'updateOne': {
'filter': {
'_id': post._id
},
'update': {
'$set': {
'quotes': threadQuotes,
'message': message
}
}
}
});
}
}));
if (all === false) {
//get posts that quoted deleted posts so we can remarkup them
if (backlinkRebuilds.size > 0) {
const remarkupPosts = await Posts.globalGetPosts([...backlinkRebuilds]);
await Promise.all(remarkupPosts.map(async post => { //doing these all at once
if (post.nomarkup && post.nomarkup.length > 0) { //is this check even necessary? how would it have a quote with no message
//redo the markup
let message = simpleMarkdown(post.nomarkup);
const { quotedMessage, threadQuotes } = await linkQuotes(post.board, post.nomarkup, post.thread);
message = sanitize(quotedMessage, sanitizeOptions);
bulkWrites.push({
'updateOne': {
'filter': {
'_id': post._id
},
'update': {
'$set': {
'quotes': threadQuotes,
'message': message
}
}
}
});
}
}));
}
}
//bulkwrite it all

@ -358,7 +358,7 @@ module.exports = async (req, res, next) => {
'board': res.locals.board._id
});
//if its above the trigger
if (tph > tphTrigger) {
if (tph >= tphTrigger) {
//update in memory for other stuff done e.g. rebuilds
const update = {
'$set': {}

@ -102,6 +102,19 @@ block content
input(type='text' name='ban_duration' placeholder='e.g. 1w' value=board.settings.filterBanDuration)
input(type='submit', value='save settings')
hr(size=1)
h4.no-m-p Delete board:
section.form-wrapper.flexleft.mv-10
form.form-post(action=`/forms/board/${board._id}/deleteboard`, enctype='application/x-www-form-urlencoded', method='POST')
input(type='hidden' name='_csrf' value=csrf)
section.row
.label I'm sure
label.postform-style.ph-5
input(type='checkbox', name='confirm', value='true' required)
section.row
.label Board URI
input(type='text' name='uri' required)
input(type='submit', value='submit')
hr(size=1)
h4.no-m-p Add Banners:
section.form-wrapper.flexleft.mv-10
form.form-post(action=`/forms/board/${board._id}/addbanners`, enctype='multipart/form-data', method='POST')

Loading…
Cancel
Save