diff --git a/configs/main.js.example b/configs/main.js.example index ebba76d4..0cdef370 100644 --- a/configs/main.js.example +++ b/configs/main.js.example @@ -246,6 +246,7 @@ module.exports = { tphTrigger: 0, //numebr of threads in an hour before trigger action is activated pphTrigger: 0, //number of posts in an hour before ^ triggerAction: 0, //0=nothing, 1=captcha enable for threads, 2=captcha enable for all posts, 3=lock board + resetTrigger: false, //reset captcha/lock settings back to original at the end of hour forceAnon: false, //disable name and subject, only allow sage email sageOnlyEmail: false, //only allow sage email early404: true, //delete threads beyond the first 1/3 of pages with less than 5 replies diff --git a/db/boards.js b/db/boards.js index 66a2ad9f..c61faff9 100644 --- a/db/boards.js +++ b/db/boards.js @@ -57,6 +57,7 @@ module.exports = { deleteOne: (board) => { cache.del(`board:${board}`); cache.del(`banners:${board}`); + cache.srem('triggered', board); return db.deleteOne({ '_id': board }); }, @@ -210,6 +211,31 @@ module.exports = { next(); }, + triggerModes: (boards) => { + return db.aggregate([ + { + '$match': { + '_id': { + '$in': boards + } + } + }, { + '$project': { + '_id': 1, + 'lockMode': { + 'new': '$settings.lockMode', + 'old': '$preTriggerMode.lockMode' + }, + 'captchaMode': { + 'new': '$settings.captchaMode', + 'old': '$preTriggerMode.captchaMode' + }, + 'threadLimit': '$settings.threadLimit' + } + } + ]).toArray(); + }, + getNextId: async (board, saged) => { const update = { '$inc': { diff --git a/helpers/tasks.js b/helpers/tasks.js index 86940f5d..8fbc5ae9 100644 --- a/helpers/tasks.js +++ b/helpers/tasks.js @@ -6,7 +6,9 @@ const Mongo = require(__dirname+'/../db/db.js') , { remove } = require('fs-extra') , { debugLogs, pruneModlogs, pruneAfterDays, enableWebring, maxRecentNews } = require(__dirname+'/../configs/main.js') , { Stats, Posts, Files, Boards, News, Modlogs } = require(__dirname+'/../db/') + , cache = require(__dirname+'/../redis.js') , render = require(__dirname+'/render.js') + , buildQueue = require(__dirname+'/../queue.js') , timeDiffString = require(__dirname+'/timediffstring.js'); module.exports = { @@ -86,6 +88,9 @@ module.exports = { //building multiple pages (for rebuilds) buildBoardMultiple: async (options) => { const start = process.hrtime(); + if (!options.board._id) { + options.board = await Boards.findOne(options.board); + } const maxPage = Math.min(Math.ceil((await Posts.getPages(options.board._id)) / 10), Math.ceil(options.board.settings.threadLimit/10)) || 1; if (options.endpage === 0) { //deleted only/all posts, so only 1 page will remain @@ -216,9 +221,66 @@ module.exports = { const start = process.hrtime(); await Stats.updateBoards(); await Stats.resetStats(); + buildQueue.push({ + 'task': 'buildHomepage', + }); + const end = process.hrtime(start); + debugLogs && console.log(timeDiffString(label, end)); + module.exports.resetTriggers(); + }, + + resetTriggers: async() => { + const label = 'Resetting pph/tph triggers'; + const start = process.hrtime(); + const triggeredBoards = await cache.sgetall('triggered'); //boards triggered pph/tph mode + await cache.del('triggered'); + if (triggeredBoards.length > 0) { + const triggerModes = await Boards.triggerModes(triggeredBoards); + const bulkWrites = triggerModes.map(p => { + return { + 'updateOne': { + 'filter': { + '_id': p._id + }, + 'update': { + '$set': { + 'settings.lockMode': p.lockMode.old, + 'settings.captchaMode': p.captchaMode.old + } + } + } + } + }) + await Boards.db.bulkWrite(bulkWrites); + const promises = []; + triggerModes.forEach(async (p) => { + await cache.del(`board:${p._id}`); + if (p.captchaMode.old < p.captchaMode.new) { + if (p.captchaMode.old === 2) { + promises.push(remove(`${uploadDirectory}/html/${p._id}/thread/`)); + } + if (p.captchaMode.old === 0) { + buildQueue.push({ + 'task': 'buildBoardMultiple', + 'options': { + 'board': p._id, + 'startpage': 1, + 'endpage': Math.ceil(p.threadLimit/10) + } + }) + buildQueue.push({ + 'task': 'buildCatalog', + 'options': { + 'board': p._id + } + }); + } + } + }) + await Promise.all(promises); + } const end = process.hrtime(start); debugLogs && console.log(timeDiffString(label, end)); - module.exports.buildHomepage(); }, buildChangePassword: () => { diff --git a/migrations/index.js b/migrations/index.js index e529a55b..84138a67 100644 --- a/migrations/index.js +++ b/migrations/index.js @@ -8,4 +8,5 @@ module.exports = { '0.0.5': require(__dirname+'/migration-0.0.5.js'), //add bumplimit to board settings '0.0.6': require(__dirname+'/migration-0.0.6.js'), //add blocked countries to board settings '0.0.7': require(__dirname+'/migration-0.0.7.js'), //sage only email without force anon for some reason + '0.0.8': require(__dirname+'/migration-0.0.8.js'), //option to auto reset triggers after hour is over } diff --git a/migrations/migration-0.0.8.js b/migrations/migration-0.0.8.js new file mode 100644 index 00000000..66d79f11 --- /dev/null +++ b/migrations/migration-0.0.8.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = async(db, redis) => { + console.log('add resetTrigger option to boards'); + await db.collection('boards').updateMany({}, { + '$set': { + 'settings.resetTrigger': false, + } + }); + console.log('Cleared boards cache'); + await redis.deletePattern('board:*'); +}; diff --git a/models/forms/changeboardsettings.js b/models/forms/changeboardsettings.js index 18d56a7a..e4480438 100644 --- a/models/forms/changeboardsettings.js +++ b/models/forms/changeboardsettings.js @@ -92,6 +92,7 @@ module.exports = async (req, res, next) => { 'forceReplyFile': booleanSetting(req.body.force_reply_file), 'forceThreadSubject': booleanSetting(req.body.force_thread_subject), 'disableReplySubject': booleanSetting(req.body.disable_reply_subject), + 'resetTrigger': booleanSetting(req.body.reset_trigger), 'captchaMode': numberSetting(req.body.captcha_mode, oldSettings.captchaMode), 'tphTrigger': numberSetting(req.body.tph_trigger, oldSettings.tphTrigger), 'pphTrigger': numberSetting(req.body.pph_trigger, oldSettings.pphTrigger), @@ -128,7 +129,11 @@ module.exports = async (req, res, next) => { //settings changed in the db await Boards.updateOne(req.params.board, { '$set': { - 'settings': newSettings + 'settings': newSettings, + 'preTriggerMode': { + 'lockMode': newSettings.lockMode, + 'captchaMode': newSettings.captchaMode + } } }); diff --git a/models/forms/makepost.js b/models/forms/makepost.js index 4dc373b3..d7712206 100644 --- a/models/forms/makepost.js +++ b/models/forms/makepost.js @@ -44,7 +44,7 @@ module.exports = async (req, res, next) => { let redirect = `/${req.params.board}/` let salt = null; let thread = null; - const { filterBanDuration, filterMode, filters, blockedCountries, + const { filterBanDuration, filterMode, filters, blockedCountries, resetTrigger, maxFiles, sageOnlyEmail, forceAnon, replyLimit, disableReplySubject, threadLimit, ids, userPostSpoiler, pphTrigger, tphTrigger, triggerAction, captchaMode, lockMode, allowedFileTypes, flags } = res.locals.board.settings; @@ -386,7 +386,12 @@ module.exports = async (req, res, next) => { || (pphTrigger > 0 && hourPosts.pph > pphTrigger)) { //update in memory for other stuff done e.g. rebuilds const update = { - '$set': {} + '$set': { + 'preTriggerMode': { + lockMode, + captchaMode + } + } }; if (triggerAction < 3) { res.locals.board.settings.captchaMode = triggerAction; @@ -401,6 +406,10 @@ module.exports = async (req, res, next) => { } //set it in the db await Boards.updateOne(res.locals.board._id, update); + if (resetTrigger) { + //mark the board as being triggered so we can return it to old mode after on schedule + await cache.sadd('triggered', res.locals.board._id); + } } } diff --git a/package.json b/package.json index 6662aa63..aea16e71 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jschan", "version": "0.0.1", - "migrateVersion": "0.0.7", + "migrateVersion": "0.0.8", "description": "", "main": "server.js", "dependencies": { diff --git a/queue.js b/queue.js index fa19544a..38300445 100644 --- a/queue.js +++ b/queue.js @@ -1,8 +1,8 @@ 'use strict'; const Queue = require('bull') - , configs = require(__dirname+'/configs/main.js') - , taskQueue = new Queue('task', { 'redis': configs.redis }); + , { redis } = require(__dirname+'/configs/main.js') + , taskQueue = new Queue('task', { redis }); module.exports = { diff --git a/redis.js b/redis.js index 350d3666..f203848f 100644 --- a/redis.js +++ b/redis.js @@ -27,6 +27,16 @@ module.exports = { return client.sadd(key, value); }, + //get all members of a set + sgetall: (key) => { + return client.smembers(key); + }, + + //remove an item from a set + srem: (key, value) => { + return client.srem(key, value); + }, + //get random item from set srand: (key) => { return client.srandmember(key); diff --git a/views/pages/managesettings.pug b/views/pages/managesettings.pug index ae292ea1..2d76895e 100644 --- a/views/pages/managesettings.pug +++ b/views/pages/managesettings.pug @@ -208,6 +208,10 @@ block content option(value='2', selected=board.settings.triggerAction === 2) Enable captcha for all posts option(value='3', selected=board.settings.triggerAction === 3) Lock thread creation option(value='4', selected=board.settings.triggerAction === 4) Lock board + .row + .label Auto Reset Trigger + label.postform-style.ph-5 + input(type='checkbox', name='reset_trigger', value='true' checked=board.settings.resetTrigger) .row .label Early 404 label.postform-style.ph-5 diff --git a/worker.js b/worker.js index 9f5cdcf4..5ee7fa2e 100644 --- a/worker.js +++ b/worker.js @@ -4,8 +4,7 @@ process .on('uncaughtException', console.error) .on('unhandledRejection', console.error); -const Queue = require('bull') - , { redis, debugLogs } = require(__dirname+'/configs/main.js') +const { debugLogs } = require(__dirname+'/configs/main.js') , Mongo = require(__dirname+'/db/db.js'); (async () => { @@ -14,13 +13,13 @@ const Queue = require('bull') await Mongo.connect(); const tasks = require(__dirname+'/helpers/tasks.js') - , taskQueue = new Queue('task', { redis }); + , { queue } = require(__dirname+'/queue.js') - taskQueue + queue .on('error', console.error) .on('failed', console.warn); - taskQueue.process(async job => { + queue.process(async job => { await tasks[job.data.task](job.data.options); return null; });