diff --git a/CHANGELOG.md b/CHANGELOG.md index cf62ed03..ff134d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,3 +66,5 @@ - Much improved nginx configuration for installation, script does most of the work - Fixed settings form asking to save password -.- - Multiple files & post flags now shown in catalog view + - Faster, more efficient global settings changes + - Add option for board owner to prevent OP deleting threads that are too old or have too many replies diff --git a/configs/template.js.example b/configs/template.js.example index 02bb6437..a03f27b4 100644 --- a/configs/template.js.example +++ b/configs/template.js.example @@ -406,6 +406,8 @@ module.exports = { filters: [], //words/phrases to block filterMode: 0, //0=nothing, 1=prevent post, 2=auto ban filterBanDuration: 0, //duration (in ms) to ban if filter mode=2 + deleteProtectionAge: 0, //prevent non-staff OP from deleting their thread if it older than this age in ms + deleteProtectionCount: 0, //prevent non-staff op deleting their thread if it has more than this many replies strictFiltering: false, announcement: { raw: null, diff --git a/controllers/forms/boardsettings.js b/controllers/forms/boardsettings.js index 21570088..6cf8566b 100644 --- a/controllers/forms/boardsettings.js +++ b/controllers/forms/boardsettings.js @@ -1,3 +1,4 @@ + 'use strict'; const changeBoardSettings = require(__dirname+'/../../models/forms/changeboardsettings.js') @@ -12,12 +13,12 @@ const changeBoardSettings = require(__dirname+'/../../models/forms/changeboardse module.exports = { paramConverter: paramConverter({ - timeFields: ['ban_duration'], + timeFields: ['ban_duration', 'delete_protection_age'], trimFields: ['filters', 'moderators', 'tags', 'announcement', 'description', 'name', 'custom_css'], allowedArrays: ['countries'], numberFields: ['lock_reset', 'captcha_reset', 'filter_mode', 'lock_mode', 'message_r9k_mode', 'file_r9k_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'pph_trigger_action', 'tph_trigger_action', 'bump_limit', 'reply_limit', 'max_files', 'thread_limit', 'max_thread_message_length', 'max_reply_message_length', 'min_thread_message_length', - 'min_reply_message_length'], + 'min_reply_message_length', 'delete_protection_count'], }), controller: async (req, res, next) => { @@ -68,6 +69,8 @@ module.exports = { { result: numberBody(req.body.lock_reset, 0, 2), expected: true, error: 'Invalid trigger reset lock' }, { result: numberBody(req.body.captcha_reset, 0, 2), expected: true, error: 'Invalid trigger reset captcha' }, { result: numberBody(req.body.ban_duration, 0), expected: true, error: 'Invalid filter auto ban duration' }, + { result: numberBody(req.body.delete_protection_age, 0), expected: true, error: 'Invalid OP thread age delete protection' }, + { result: numberBody(req.body.delete_protection_count, 0), expected: true, error: 'Invalid OP thread reply count delete protection' }, { result: inArrayBody(req.body.theme, themes), expected: true, error: 'Invalid theme' }, { result: inArrayBody(req.body.code_theme, codeThemes), expected: true, error: 'Invalid code theme' }, ], res.locals.permLevel); diff --git a/migrations/0.1.8.js b/migrations/0.1.8.js new file mode 100644 index 00000000..25329e09 --- /dev/null +++ b/migrations/0.1.8.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = async(db, redis) => { + console.log('Adding OP delete protection options to board settings'); + await db.collection('boards').updateMany({}, { + '$set': { + 'settings.deleteProtectionAge': 0, + 'settings.deleteProtectionCount': 0, + } + }); + console.log('Clearing boards cache'); + await redis.deletePattern('board:*'); + +}; diff --git a/models/forms/actionhandler.js b/models/forms/actionhandler.js index d301aa16..cbaa3e45 100644 --- a/models/forms/actionhandler.js +++ b/models/forms/actionhandler.js @@ -56,7 +56,7 @@ module.exports = async (req, res, next) => { redirect, }); } - res.locals.posts = passwordPosts + res.locals.posts = passwordPosts; } //affected boards, list and page numbers @@ -96,6 +96,26 @@ module.exports = async (req, res, next) => { messages.push(message); } if (deleting) { + if (res.locals.permLevel >= 4) { + //delete protection. this could only be single board actions obvously with permLevel >=4 + const { deleteProtectionAge, deleteProtectionCount } = threadBoards[res.locals.posts[0].board].settings; + if (deleteProtectionAge > 0 || deleteProtectionCount > 0) { + const protectedThread = res.locals.posts.some(p => { + return p.thread === null //is a thread + && ((deleteProtectionCount > 0 && p.replyposts > deleteProtectionCount) //and it has more replies than the protection count + || (deleteProtectionAge > 0 && new Date(p.date + deleteProtectionAge) > new Date())); //or was created too recently + }); + if (protectedThread != null) { + //alternatively, the above .some() could become a filter like some other options and silently not delete, + //but i think in this case it would be important to notify the user that their own thread(s) cant be deleted yet + return dynamicResponse(req, res, 403, 'message', { + 'title': 'Forbidden', + 'error': 'You cannot delete old threads or threads with too many replies', + redirect, + }); + } + } + } const postsBefore = res.locals.posts.length; if (req.body.delete_ip_board || req.body.delete_ip_global || req.body.delete_ip_thread) { const deletePostIps = res.locals.posts.map(x => x.ip.single); diff --git a/models/forms/changeboardsettings.js b/models/forms/changeboardsettings.js index 5155d485..b02637fa 100644 --- a/models/forms/changeboardsettings.js +++ b/models/forms/changeboardsettings.js @@ -122,6 +122,8 @@ module.exports = async (req, res, next) => { 'fileR9KMode': numberSetting(req.body.file_r9k_mode, oldSettings.fileR9KMode), 'filterMode': numberSetting(req.body.filter_mode, oldSettings.filterMode), 'filterBanDuration': numberSetting(req.body.ban_duration, oldSettings.filterBanDuration), + 'deleteProtectionAge': numberSetting(req.body.delete_protection_age, oldSettings.deleteProtectionAge), + 'deleteProtectionCount': numberSetting(req.body.delete_protection_count, oldSettings.deleteProtectionCount), 'filters': arraySetting(req.body.filters, oldSettings.filters, 50), 'blockedCountries': req.body.countries || [], 'disableAnonymizerFilePosting': booleanSetting(req.body.disable_anonymizer_file_posting), diff --git a/package.json b/package.json index 3b5d0040..c86fb75a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jschan", "version": "0.1.8", - "migrateVersion": "0.1.6", + "migrateVersion": "0.1.8", "description": "", "main": "server.js", "dependencies": { diff --git a/views/pages/managesettings.pug b/views/pages/managesettings.pug index 246bc9d7..dae52ced 100644 --- a/views/pages/managesettings.pug +++ b/views/pages/managesettings.pug @@ -212,6 +212,12 @@ block content option(value='0', selected=board.settings.messageR9KMode === 0) Off option(value='1', selected=board.settings.messageR9KMode === 1) Per Thread option(value='2', selected=board.settings.messageR9KMode === 2) Board Wide + .row + .label OP Reply Count Delete Protection + input(type='number' name='delete_protection_count' value=board.settings.deleteProtectionCount) + .row + .label OP Thread Age Delete Protection + input(type='text' name='delete_protection_age' placeholder='e.g. 1w' value=board.settings.deleteProtectionAge) .col.w900 .row h4.mv-5 Antispam