diff --git a/configs/main.js.example b/configs/main.js.example index 0f38a51c..1c3b3be5 100644 --- a/configs/main.js.example +++ b/configs/main.js.example @@ -290,6 +290,7 @@ module.exports = { codeTheme: 'ir-black', sfw: false, //safe for work board lockMode: 0, //board lock mode + fileR9KMode: 0, //r9k for files, 0=off, 1=per thread, 2=whole board unlistedLocal: false, //board hidden from on-site board list and frontpage unlistedWebring: false, //board hidden from webring captchaMode: 0, //0=disabled, 1=for threads, 2=for all posts diff --git a/db/posts.js b/db/posts.js index e370f890..3681f8a8 100644 --- a/db/posts.js +++ b/db/posts.js @@ -296,6 +296,27 @@ module.exports = { }, + checkExistingFiles: async (board, thread = null, hashes) => { + const query = { + 'board': board, + 'files.hash': { + '$in': hashes + } + } + if (thread !== null) { + query['$or'] = [ + { 'thread': thread }, + { 'postId': thread }, + ] + } + const postWithExistingFiles = await db.findOne(query, { + 'projection': { + 'files.hash': 1, + } + }); + return postWithExistingFiles; + }, + allBoardPosts: (board) => { return db.find({ 'board': board diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js index e0b1f809..b8b56ae6 100644 --- a/helpers/paramconverter.js +++ b/helpers/paramconverter.js @@ -5,7 +5,7 @@ const { ObjectId } = require(__dirname+'/../db/db.js') 'checkedreports', 'checkedbans', 'checkedbanners', 'checkedaccounts', 'countries']) //only these should be arrays, since express bodyparser can output arrays , trimFields = ['tags', 'uri', 'moderators', 'filters', 'announcement', 'description', 'message', 'name', 'subject', 'email', 'postpassword', 'password', 'default_name', 'report_reason', 'ban_reason', 'log_message', 'custom_css'] //trim if we dont want filed with whitespace - , numberFields = ['filter_mode', 'lock_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'trigger_action', 'bump_limit', 'reply_limit', 'move_to_thread',, 'postId', + , numberFields = ['filter_mode', 'lock_mode', 'file_r9k_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'trigger_action', 'bump_limit', 'reply_limit', 'move_to_thread',, 'postId', 'max_files', 'thread_limit', 'thread', 'max_thread_message_length', 'max_reply_message_length', 'min_thread_message_length', 'min_reply_message_length', 'auth_level'] //convert these to numbers before they hit our routes , banDurationRegex = /^(?[\d]+y)?(?[\d]+m)?(?[\d]+w)?(?[\d]+d)?(?[\d]+h)?$/ , timeUtils = require(__dirname+'/timeutils.js') diff --git a/migrations/index.js b/migrations/index.js index 6dbd6a62..6cdd6bfe 100644 --- a/migrations/index.js +++ b/migrations/index.js @@ -13,4 +13,5 @@ module.exports = { '0.0.10': require(__dirname+'/migration-0.0.10.js'), //add links to modlog for new logs '0.0.11': require(__dirname+'/migration-0.0.11.js'), //rename captcha "text" field to "answer" since we support multiple captcha types now '0.0.12': require(__dirname+'/migration-0.0.12.js'), //yotsuba b -> yotsuba-b + '0.0.13': require(__dirname+'/migration-0.0.13.js'), //add r9k mode } diff --git a/migrations/migration-0.0.13.js b/migrations/migration-0.0.13.js new file mode 100644 index 00000000..82dc45b9 --- /dev/null +++ b/migrations/migration-0.0.13.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = async(db, redis) => { + console.log('Adding file r9k setting to boards db'); + await db.collection('boards').updateMany({}, { + '$set': { + 'settings.fileR9KMode': 0, + } + }); + console.log('Cleared boards cache'); + await redis.deletePattern('board:*'); +}; diff --git a/models/forms/changeboardsettings.js b/models/forms/changeboardsettings.js index 5761c39c..3377ed95 100644 --- a/models/forms/changeboardsettings.js +++ b/models/forms/changeboardsettings.js @@ -107,6 +107,7 @@ module.exports = async (req, res, next) => { 'maxThreadMessageLength': numberSetting(req.body.max_thread_message_length, oldSettings.maxThreadMessageLength), 'maxReplyMessageLength': numberSetting(req.body.max_reply_message_length, oldSettings.maxReplyMessageLength), 'lockMode': numberSetting(req.body.lock_mode, oldSettings.lockMode), + '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), 'tags': arraySetting(req.body.tags, oldSettings.tags, 10), diff --git a/models/forms/makepost.js b/models/forms/makepost.js index 3541d516..272e4eac 100644 --- a/models/forms/makepost.js +++ b/models/forms/makepost.js @@ -50,7 +50,7 @@ module.exports = async (req, res, next) => { 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; + captchaMode, lockMode, allowedFileTypes, flags, fileR9KMode } = res.locals.board.settings; if (res.locals.permLevel >= 4 && res.locals.country && blockedCountries.includes(res.locals.country.code)) { @@ -161,6 +161,22 @@ module.exports = async (req, res, next) => { let files = []; // if we got a file if (res.locals.numFiles > 0) { + if ((req.body.thread && fileR9KMode === 1) || fileR9KMode === 2) { + const filesHashes = req.files.file.map(f => f.sha256); + const postWithExistingFiles = await Posts.checkExistingFiles(res.locals.board._id, (fileR9KMode === 2 ? null : req.body.thread), filesHashes); + if (postWithExistingFiles != null) { + await deleteTempFiles(req).catch(e => console.error); + const conflictingFiles = req.files.file + .filter(f => postWithExistingFiles.files.some(fx => fx.hash === f.sha256)) + .map(f => f.name) + .join(', '); + return dynamicResponse(req, res, 409, 'message', { + 'title': 'Conflict', + 'message': `Uploaded files must be unique ${fileR9KMode === 1 ? 'in this thread' : 'on this board'}.\nAt least the following file${conflictingFiles.length > 1 ? 's are': ' is'} not unique: ${conflictingFiles}`, + 'redirect': redirect + }); + } + } // check all mime types before we try saving anything for (let i = 0; i < res.locals.numFiles; i++) { if (!mimeTypes.allowed(req.files.file[i].mimetype, allowedFileTypes)) { diff --git a/package.json b/package.json index 6809e3f2..d1fc9863 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jschan", "version": "0.0.1", - "migrateVersion": "0.0.12", + "migrateVersion": "0.0.13", "description": "", "main": "server.js", "dependencies": { diff --git a/views/pages/managesettings.pug b/views/pages/managesettings.pug index 60557fd0..31b27625 100644 --- a/views/pages/managesettings.pug +++ b/views/pages/managesettings.pug @@ -161,6 +161,12 @@ block content option(value='0', selected=board.settings.lockMode === 0) Unlocked option(value='1', selected=board.settings.lockMode === 1) Lock thread creation option(value='2', selected=board.settings.lockMode === 2) Lock board + .row + .label Unique Files + select(name='file_r9k_mode') + option(value='0', selected=board.settings.fileR9KMode === 0) Off + option(value='1', selected=board.settings.fileR9KMode === 1) Per Thread + option(value='2', selected=board.settings.fileR9KMode === 2) Board Wide .row .label Unlist locally label.postform-style.ph-5