From c54c9e78924b1046303053fce21b332593277a8f Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 10:33:12 +0000 Subject: [PATCH 01/26] multi board rebuilds and rebuilds work for global actions untested --- build.js | 6 +- controllers/forms.js | 197 +++++++++++++++------------------- db/boards.js | 2 +- helpers/actionchecker.js | 30 +++--- helpers/haspermsmiddleware.js | 3 +- models/forms/actionhandler.js | 154 ++++++++++---------------- server.js | 1 + 7 files changed, 162 insertions(+), 231 deletions(-) diff --git a/build.js b/build.js index 303dc3c5..2157aa2d 100644 --- a/build.js +++ b/build.js @@ -8,6 +8,10 @@ const Posts = require(__dirname+'/db/posts.js') module.exports = { buildCatalog: async (board) => { +//console.log('building catalog', `${board._id}/catalog.html`); + if (!board._id) { + board = await Boards.findOne(board); + } const threads = await Posts.getCatalog(board._id); return render(`${board._id}/catalog.html`, 'catalog.pug', { board, @@ -22,7 +26,7 @@ module.exports = { } const thread = await Posts.getThread(board._id, threadId) if (!thread) { - return; //this thread may have been an OP that was deleted during a rebuild + return; //this thread may have been an OP that was deleted } return render(`${board._id}/thread/${threadId}.html`, 'thread.pug', { board, diff --git a/controllers/forms.js b/controllers/forms.js index cf31baa2..a57ec023 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -4,15 +4,8 @@ const express = require('express') , router = express.Router() , Boards = require(__dirname+'/../db/boards.js') , Posts = require(__dirname+'/../db/posts.js') - , Captchas = require(__dirname+'/../db/captchas.js') - , Trips = require(__dirname+'/../db/trips.js') - , Bans = require(__dirname+'/../db/bans.js') , Mongo = require(__dirname+'/../db/db.js') , remove = require('fs-extra').remove - , deletePosts = require(__dirname+'/../models/forms/delete-post.js') - , spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js') - , dismissGlobalReports = require(__dirname+'/../models/forms/dismissglobalreport.js') - , banPoster = require(__dirname+'/../models/forms/ban-poster.js') , removeBans = require(__dirname+'/../models/forms/removebans.js') , makePost = require(__dirname+'/../models/forms/make-post.js') , uploadBanners = require(__dirname+'/../models/forms/uploadbanners.js') @@ -21,6 +14,7 @@ const express = require('express') , changePassword = require(__dirname+'/../models/forms/changepassword.js') , registerAccount = require(__dirname+'/../models/forms/register.js') , checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js') + , checkPerms = require(__dirname+'/../helpers/hasperms.js') , paramConverter = require(__dirname+'/../helpers/paramconverter.js') , banCheck = require(__dirname+'/../helpers/bancheck.js') , deletePostFiles = require(__dirname+'/../helpers/files/deletepostfiles.js') @@ -346,44 +340,73 @@ router.post('/board/:board/deletebanners', csrf, Boards.exists, checkPermsMiddle }); -//report/delete/spoiler/ban -router.post('/board/:board/actions', Boards.exists, banCheck, paramConverter, verifyCaptcha, actionHandler); //Captcha on regular actions -router.post('/board/:board/modactions', csrf, Boards.exists, checkPermsMiddleware, paramConverter, actionHandler); //CSRF for mod actions +//actions for a specific board +router.post('/board/:board/actions', Boards.exists, banCheck, paramConverter, verifyCaptcha, boardActionController); //Captcha on regular actions +router.post('/board/:board/modactions', csrf, Boards.exists, checkPermsMiddleware, paramConverter, boardActionController); //CSRF for mod actions +async function boardActionController(req, res, next) { -//unban -router.post('/board/:board/unban', csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => { - - //keep this for later in case i add other options to unbans const errors = []; - if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) { - errors.push('Must select 1-10 bans') + //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'); + } + + res.locals.actions = actionChecker(req); + + //make sure they selected at least 1 action + if (!res.locals.actions.anyValid) { + errors.push('No actions selected'); + } + //check if they have permission to perform the actions + res.locals.hasPerms = checkPerms(req, res); + if(!res.locals.hasPerms && res.locals.actions.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'); + } + 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.report || req.body.global_report) && (!req.body.report_reason || req.body.report_reason.length === 0)) { + errors.push('Reports must have a reason'); } if (errors.length > 0) { return res.status(400).render('message', { 'title': 'Bad request', 'errors': errors, - 'redirect': `/${req.params.board}/manage.html` - }); + 'redirect': `/${req.params.board}/` + }) + } + + res.locals.posts = await Posts.getPosts(req.params.board, req.body.checkedposts, true); + if (!res.locals.posts || res.locals.posts.length === 0) { + return res.status(404).render('message', { + 'title': 'Not found', + 'error': 'Selected posts not found', + 'redirect': `/${req.params.board}/` + }) } - const messages = []; try { - messages.push((await removeBans(req, res, next))); + await actionHandler(req, res, next); } catch (err) { + console.error(err); return next(err); } - return res.render('message', { - 'title': 'Success', - 'messages': messages, - 'redirect': `/${req.params.board}/manage.html` - }); - -}); +} -router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async(req, res, next) => { +//global actions (global manage page) +router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, globalActionController); +async function globalActionController(req, res, next) { const errors = []; @@ -392,10 +415,10 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async errors.push('Must select 1-10 posts') } - const { anyGlobal } = actionChecker(req); + res.locals.actions = actionChecker(req); - //make sure they selected at least 1 global action - if (!anyGlobal) { + //make sure they have any global actions, and that they only selected global actions + if (!res.locals.actions.anyGlobal || res.locals.actions.anyValid > res.locals.actions.anyGlobal) { errors.push('Invalid actions selected'); } @@ -403,15 +426,9 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async 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.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) { @@ -423,8 +440,8 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async } //get posts with global ids only - const posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true); - if (!posts || posts.length === 0) { + res.locals.posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true); + if (!res.locals.posts || res.locals.posts.length === 0) { return res.status(404).render('message', { 'title': 'Not found', 'errors': 'Selected posts not found', @@ -432,80 +449,36 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async }) } - //get the ids - const postMongoIds = posts.map(post => Mongo.ObjectId(post._id)); + try { + await actionHandler(req, res, next); + } catch (err) { + console.error(err); + return next(err); + } + +} + +//unban +router.post('/board/:board/unban', csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => { + + //keep this for later in case i add other options to unbans + const errors = []; + + if (!req.body.checkedbans || req.body.checkedbans.length === 0 || req.body.checkedbans.length > 10) { + errors.push('Must select 1-10 bans') + } + + if (errors.length > 0) { + return res.status(400).render('message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}/manage.html` + }); + } + const messages = []; - const combinedQuery = {}; - let aggregateNeeded = false; try { - if (req.body.global_ban) { - const { message, action, query } = await banPoster(req, res, next, null, posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); - } - if (req.body.delete_ip_global) { - const deletePostIps = posts.map(x => x.ip); - const deleteIpPosts = await Posts.db.find({ - 'ip': { - '$in': deletePostIps - } - }).toArray(); - if (deleteIpPosts && deleteIpPosts.length > 0) { - const { message } = await deletePosts(req, res, next, deleteIpPosts, null); - messages.push(message); - aggregateNeeded = true; - } - } else if (req.body.delete) { - const { message } = await deletePosts(req, res, next, posts); - messages.push(message); - aggregateNeeded = true; - } else { - // if it was getting deleted, we cant do any of these - if (req.body.delete_file) { - const { message, action, query } = await deletePostsFiles(posts); - if (action) { - aggregateNeeded = true; - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); - } else if (req.body.spoiler) { - const { message, action, query } = spoilerPosts(posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); - } - if (req.body.global_dismiss) { - const { message, action, query } = dismissGlobalReports(posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); - } - } - if (Object.keys(combinedQuery).length > 0) { - await Posts.db.updateMany({ - '_id': { - '$in': postMongoIds - } - }, combinedQuery); - } - if (aggregateNeeded) { - const threadsToUpdate = [...new Set(posts.filter(post => post.thread !== null))]; - //recalculate and set correct aggregation numbers again - await Promise.all(threadsToUpdate.map(async (post) => { - const replyCounts = await Posts.getReplyCounts(post.board, post.thread); - let replyposts = 0; - let replyfiles = 0; - if (replyCounts[0]) { - replyposts = replyCounts[0].replyposts; - replyfiles = replyCounts[0].replyfiles; - } - Posts.setReplyCounts(post.board, post.thread, replyposts, replyfiles); - })); - } + messages.push((await removeBans(req, res, next))); } catch (err) { return next(err); } @@ -513,7 +486,7 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async return res.render('message', { 'title': 'Success', 'messages': messages, - 'redirect': '/globalmanage.html' + 'redirect': `/${req.params.board}/manage.html` }); }); diff --git a/db/boards.js b/db/boards.js index 0e225f46..e0ec5baf 100644 --- a/db/boards.js +++ b/db/boards.js @@ -5,7 +5,7 @@ const Mongo = require(__dirname+'/db.js') module.exports = { - db, + db: db.collection('boards'), findOne: (name) => { return db.collection('boards').findOne({ '_id': name }); diff --git a/helpers/actionchecker.js b/helpers/actionchecker.js index 10ab2486..be9231ac 100644 --- a/helpers/actionchecker.js +++ b/helpers/actionchecker.js @@ -1,16 +1,16 @@ 'use strict'; const actions = [ + {name:'delete_file', global:true, auth:false, passwords:true}, + {name:'spoiler', global:true, auth:false, passwords:true}, + {name:'delete', global:true, auth:false, passwords:true}, {name:'lock', global:false, auth:true, passwords:false}, {name:'sticky', global:false, auth:true, passwords:false}, {name:'sage', 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_ip_board', global:false, auth:true, passwords:false}, {name:'delete_ip_global', global:true, auth:true, passwords:false}, - {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}, @@ -19,26 +19,24 @@ const actions = [ module.exports = (req, res) => { - let anyGlobal = false - , anyAuthed = false - , anyPasswords = false - , anyValid = false; + let anyGlobal = 0 + , anyAuthed = 0 + , anyPasswords = 0 + , anyValid = 0; 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; + anyValid++; + if (action.global) { + anyGlobal++; } - if (!anyPasswords && action.passwords) { - anyPasswords = true; + if (action.auth) { + anyAuthed++; } - if (!anyValid) { - anyValid = true; + if (action.passwords) { + anyPasswords++; } } if (anyGlobal && anyAuthed && anyValid) { diff --git a/helpers/haspermsmiddleware.js b/helpers/haspermsmiddleware.js index e928c663..8f15cbff 100644 --- a/helpers/haspermsmiddleware.js +++ b/helpers/haspermsmiddleware.js @@ -4,7 +4,8 @@ const hasPerms = require(__dirname+'/hasperms.js'); module.exports = async (req, res, next) => { - if (!hasPerms(req, res)) { + res.locals.hasPerms = hasPerms(req, res); + if (!res.locals.hasPerms) { return res.status(403).render('message', { 'title': 'Forbidden', 'message': 'You do not have permission to access this page', diff --git a/models/forms/actionhandler.js b/models/forms/actionhandler.js index d876ed0d..3a6b8a54 100644 --- a/models/forms/actionhandler.js +++ b/models/forms/actionhandler.js @@ -1,6 +1,7 @@ 'use strict'; const Posts = require(__dirname+'/../../db/posts.js') + , Boards = require(__dirname+'/../../db/boards.js') , Mongo = require(__dirname+'/../../db/db.js') , banPoster = require(__dirname+'/ban-poster.js') , deletePosts = require(__dirname+'/delete-post.js') @@ -13,73 +14,17 @@ const Posts = require(__dirname+'/../../db/posts.js') , globalReportPosts = require(__dirname+'/globalreportpost.js') , dismissReports = require(__dirname+'/dismiss-report.js') , dismissGlobalReports = require(__dirname+'/dismissglobalreport.js') - , actionChecker = require(__dirname+'/../../helpers/actionchecker.js') - , checkPerms = require(__dirname+'/../../helpers/hasperms.js') - , remove = require('fs-extra').remove - , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , { buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../build.js'); module.exports = async (req, res, next) => { - - 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'); - } - 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.report || req.body.global_report) && (!req.body.report_reason || req.body.report_reason.length === 0)) { - errors.push('Reports must have a reason'); - } - - if (errors.length > 0) { - return res.status(400).render('message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}/` - }) - } - - let posts = await Posts.getPosts(req.params.board, req.body.checkedposts, true); - if (!posts || posts.length === 0) { - return res.status(404).render('message', { - 'title': 'Not found', - 'error': 'Selected posts not found', - 'redirect': `/${req.params.board}/` - }) - } - //get the ids - const postMongoIds = posts.map(post => Mongo.ObjectId(post._id)); + const postMongoIds = res.locals.posts.map(post => Mongo.ObjectId(post._id)); let passwordPostMongoIds = []; let passwordPosts = []; - if (!hasPerms && anyPasswords) { + if (!res.locals.hasPerms && res.locals.actions.anyPasswords) { //just to avoid multiple filters and mapping, do it all here - passwordPosts = posts.filter(post => { + passwordPosts = res.locals.posts.filter(post => { if (post.password != null && post.password.length > 0 && post.password == req.body.password) { @@ -91,11 +36,11 @@ module.exports = async (req, res, next) => { return res.status(403).render('message', { 'title': 'Forbidden', 'error': 'Password did not match any selected posts', - 'redirect': `/${req.params.board}/` + 'redirect': `/${req.params.board ? req.params.board+'/' : 'globalmanage.html'}` }); } } else { - passwordPosts = posts; + passwordPosts = res.locals.posts; passwordPostMongoIds = postMongoIds; } @@ -104,24 +49,24 @@ module.exports = async (req, res, next) => { const passwordCombinedQuery = {}; let aggregateNeeded = false; try { - if (hasPerms) { + if (res.locals.hasPerms) { // if getting global banned, board ban doesnt matter if (req.body.global_ban) { - const { message, action, query } = await banPoster(req, res, next, null, posts); + const { message, action, query } = await banPoster(req, res, next, null, res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); } else if (req.body.ban) { - const { message, action, query } = await banPoster(req, res, next, req.params.board, posts); + const { message, action, query } = await banPoster(req, res, next, req.params.board, res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); } } - if (hasPerms && (req.body.delete_ip_board || req.body.delete_ip_global)) { - const deletePostIps = posts.map(x => x.ip); + if (res.locals.hasPerms && (req.body.delete_ip_board || req.body.delete_ip_global)) { + const deletePostIps = res.locals.posts.map(x => x.ip); let query = { 'ip': { '$in': deletePostIps @@ -131,7 +76,7 @@ module.exports = async (req, res, next) => { query['board'] = req.params.board; } const deleteIpPosts = await Posts.db.find(query).toArray(); - posts = posts.concat(deleteIpPosts); + res.locals.posts = res.locals.posts.concat(deleteIpPosts); if (deleteIpPosts && deleteIpPosts.length > 0) { const { message } = await deletePosts(req, res, next, deleteIpPosts, req.params.board); messages.push(message); @@ -157,24 +102,24 @@ module.exports = async (req, res, next) => { } messages.push(message); } - if (hasPerms) { + if (res.locals.hasPerms) { //lock, sticky, sage if (req.body.sage) { - const { message, action, query } = sagePosts(posts); + const { message, action, query } = sagePosts(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); } if (req.body.lock) { - const { message, action, query } = lockPosts(posts); + const { message, action, query } = lockPosts(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); } if (req.body.sticky) { - const { message, action, query } = stickyPosts(posts); + const { message, action, query } = stickyPosts(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } @@ -183,13 +128,13 @@ module.exports = async (req, res, next) => { } // cannot report and dismiss at same time if (req.body.report) { - const { message, action, query } = reportPosts(req, posts); + const { message, action, query } = reportPosts(req, res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); - } else if (hasPerms && req.body.dismiss) { - const { message, action, query } = dismissReports(posts); + } else if (res.locals.hasPerms && req.body.dismiss) { + const { message, action, query } = dismissReports(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } @@ -197,13 +142,13 @@ module.exports = async (req, res, next) => { } // cannot report and dismiss at same time if (req.body.global_report) { - const { message, action, query } = globalReportPosts(req, posts); + const { message, action, query } = globalReportPosts(req, res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); - } else if (hasPerms && req.body.global_dismiss) { - const { message, action, query } = dismissGlobalReports(posts); + } else if (res.locals.hasPerms && req.body.global_dismiss) { + const { message, action, query } = dismissGlobalReports(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} } @@ -239,8 +184,8 @@ module.exports = async (req, res, next) => { //get a map of boards to threads affected const boardThreadMap = {}; const queryOrs = []; - for (let i = 0; i < posts.length; i++) { - const post = posts[i]; + for (let i = 0; i < res.locals.posts.length; i++) { + const post = res.locals.posts[i]; if (!boardThreadMap[post.board]) { boardThreadMap[post.board] = []; } @@ -263,7 +208,7 @@ module.exports = async (req, res, next) => { } //get only posts (so we can use them for thread ids - const postThreadsToUpdate = posts.filter(post => post.thread !== null); + const postThreadsToUpdate = res.locals.posts.filter(post => post.thread !== null); if (aggregateNeeded) { //recalculate replies and image counts await Promise.all(postThreadsToUpdate.map(async (post) => { @@ -296,7 +241,7 @@ module.exports = async (req, res, next) => { '$or': queryOrs }).toArray(); //combine it with what we already had - threadsEachBoard = threadsEachBoard.concat(posts.filter(post => post.thread === null)) + threadsEachBoard = threadsEachBoard.concat(res.locals.posts.filter(post => post.thread === null)) //get the oldest and newest thread for each board to determine how to delete const threadBounds = threadsEachBoard.reduce((acc, curr) => { @@ -315,36 +260,45 @@ module.exports = async (req, res, next) => { //now we need to delete outdated html //TODO: not do this for reports, handle global actions & move to separate handler + optimize and test const parallelPromises = [] - const boardsWithChanges = Object.keys(threadBounds); - for (let i = 0; i < boardsWithChanges.length; i++) { - const changeBoard = boardsWithChanges[i]; - const bounds = threadBounds[changeBoard]; + const boardNames = Object.keys(threadBounds); + const buildBoards = {}; + const multiBoards = await Boards.db.find({ + '_id': { + '$in': boardNames + } + }).toArray(); + multiBoards.forEach(board => { + buildBoards[board._id] = board; + }) + for (let i = 0; i < boardNames.length; i++) { + const boardName = boardNames[i]; + const bounds = threadBounds[boardName]; //always need to refresh catalog - parallelPromises.push(buildCatalog(res.locals.board)); + parallelPromises.push(buildCatalog(buildBoards[boardName])); //rebuild impacted threads - for (let j = 0; j < boardThreadMap[changeBoard].length; j++) { - parallelPromises.push(buildThread(boardThreadMap[changeBoard][j], changeBoard)); + for (let j = 0; j < boardThreadMap[boardName].length; j++) { + parallelPromises.push(buildThread(boardThreadMap[boardName][j], buildBoards[boardName])); } //refersh any pages affected - const afterPages = Math.ceil((await Posts.getPages(changeBoard)) / 10); - if (beforePages[changeBoard] && beforePages[changeBoard] !== afterPages) { + const afterPages = Math.ceil((await Posts.getPages(boardName)) / 10); + if (beforePages[boardName] && beforePages[boardName] !== afterPages) { //amount of pages changed, rebuild all pages - parallelPromises.push(buildBoardMultiple(res.locals.board, 1, afterPages)); + parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, afterPages)); } else { - const threadPageOldest = await Posts.getThreadPage(req.params.board, bounds.oldest); - const threadPageNewest = await Posts.getThreadPage(req.params.board, bounds.newest); + const threadPageOldest = await Posts.getThreadPage(boardName, bounds.oldest); + const threadPageNewest = await Posts.getThreadPage(boardName, bounds.newest); if (req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global) { //rebuild current and older pages for deletes - parallelPromises.push(buildBoardMultiple(res.locals.board, threadPageNewest, afterPages)); + parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, afterPages)); } else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant //rebuild current and newer pages for stickies - parallelPromises.push(buildBoardMultiple(res.locals.board, 1, threadPageOldest)); - } else if ((hasPerms && (req.body.lock || req.body.sage)) || req.body.spoiler) { + parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, threadPageOldest)); + } else if ((res.locals.hasPerms && (req.body.lock || req.body.sage)) || req.body.spoiler) { //rebuild inbewteen pages for things that dont cause page/thread movement //should rebuild only affected pages, but finding the page of all affected - //threads could end up being slower/more resource intensive. this is simpler - //but still avoids rebuilding _some_ pages unnecessarily - parallelPromises.push(buildBoardMultiple(res.locals.board, threadPageNewest, threadPageOldest)); + //threads could end up being slower/more resource intensive. this is simpler. + //it avoids rebuilding _some_ but not all pages unnecessarily + parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest)); } } } @@ -357,7 +311,7 @@ module.exports = async (req, res, next) => { return res.render('message', { 'title': 'Success', 'messages': messages, - 'redirect': `/${req.params.board}/` + 'redirect': `/${req.params.board ? req.params.board+'/' : 'globalmanage.html'}` }); } diff --git a/server.js b/server.js index eb8f1412..602dbe2d 100644 --- a/server.js +++ b/server.js @@ -115,6 +115,7 @@ const express = require('express') server.close((err) => { // if error, log and exit with error (1 code) + console.info('closing http server') if (err) { console.error(err); process.exit(1); From 8712c67ae05efd864d53d8c1ca04b832b30e50bd Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 10:33:47 +0000 Subject: [PATCH 02/26] catalog tile style changes, remove lynxchan reference from footer --- gulp/res/css/style.css | 17 +++++++------ views/includes/footer.pug | 2 +- views/mixins/catalogtile.pug | 46 +++++++++++++++++------------------- views/mixins/post.pug | 4 +++- views/pages/catalog.pug | 2 ++ wipe.js | 4 ++-- 6 files changed, 38 insertions(+), 37 deletions(-) diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css index 891b216f..37bfa3b2 100644 --- a/gulp/res/css/style.css +++ b/gulp/res/css/style.css @@ -61,6 +61,7 @@ body { box-sizing: border-box; padding: 10px; width: max-content; + width: -moz-max-content; } a, a:visited { @@ -87,7 +88,7 @@ object { background: #d6daf0; } -.catalog-tile-button { +.ecatalog-tile-button { width: 100%; line-height: 30px; float: left; @@ -98,11 +99,8 @@ object { overflow: hidden; } -.catalog-tile-content { - padding: 5px; -} - .catalog-tile { + padding: 5px; margin: 2px; text-align: center; max-height: 300px; @@ -128,7 +126,7 @@ object { .catalog { display:flex; align-items:flex-start; - justify-content: space-evenly; + justify-content: center; flex-flow: row wrap; } @@ -219,7 +217,7 @@ td, th { align-items: center; } -.post-container, .pages, .toggle-summary { +.post-container, .pages, .toggle-summary, .catalog-tile { background: #D6DAF0; border: 1px solid #B7C5D9; } @@ -260,6 +258,7 @@ td, th { display: flex; flex-flow: column wrap; width: max-content; + width: -moz-max-content; } .toggle { @@ -470,7 +469,7 @@ input textarea { input[type="text"], input[type="submit"], input[type="password"], input[type="file"], textarea { border: 1px solid #a9a9a9; font-size: inherit; - font-family: arial,helvetica,sans-serif; + font-family: arial, helvetica, sans-serif; margin: 0; flex-grow: 1; border-radius: 0px; @@ -574,7 +573,7 @@ hr { } .post-info { - background-color: #B7C5D9; + /*background-color: #B7C5D9;*/ } } diff --git a/views/includes/footer.pug b/views/includes/footer.pug index 25ab0523..2f1c38a3 100644 --- a/views/includes/footer.pug +++ b/views/includes/footer.pug @@ -1,2 +1,2 @@ .footer - a(href='https://github.com/fatchan/jschan/') not lynxchan™ + a(href='https://github.com/fatchan/jschan/') open source diff --git a/views/mixins/catalogtile.pug b/views/mixins/catalogtile.pug index 24155d6a..36379e9a 100644 --- a/views/mixins/catalogtile.pug +++ b/views/mixins/catalogtile.pug @@ -1,30 +1,28 @@ mixin catalogtile(board, post, truncate) article(class='catalog-tile') - const postURL = `/${board._id}/thread/${post.postId}.html#${post.postId}` - a.catalog-tile-button(href=postURL) Open Thread - if post.subject - span: a.no-decoration.post-subject(href=postURL) #{post.subject} - .catalog-tile-content - if post.files.length > 0 - .post-file-src - a(href=postURL) - if post.spoiler - object(data='/img/spoiler.png' width='64' height='64') - else - object.catalog-thumb(data=`/img/thumb-${post.files[0].filename.split('.')[0]}.jpg` width='64' height='64') - header.post-info - if post.sticky - img(src='/img/sticky.svg' height='12') - if post.saged - img(src='/img/saged.svg' height='12') - if post.locked - img(src='/img/locked.svg' height='12') + if post.files.length > 0 + .post-file-src + a(href=postURL) + if post.spoiler + object(data='/img/spoiler.png' width='64' height='64') + else + object.catalog-thumb(data=`/img/thumb-${post.files[0].filename.split('.')[0]}.jpg` width='64' height='64') + header.post-info + if post.sticky + img(src='/img/sticky.svg' height='12') | - span: a(href=postURL) No.#{post.postId} + if post.saged + img(src='/img/saged.svg' height='12') | - span Replies: #{post.replyposts} + if post.locked + img(src='/img/locked.svg' height='12') | - span Images: #{post.replyfiles} - if post.message - br - blockquote.no-m-p.post-message !{post.message} + span Replies: #{post.replyposts} + | + span Files: #{post.replyfiles} + br + span: a.no-decoration.post-subject(href=postURL) #{post.subject || 'No Subject'} + if post.message + br + blockquote.no-m-p.post-message !{post.message} diff --git a/views/mixins/post.pug b/views/mixins/post.pug index a8ae0fd3..7286708d 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -10,11 +10,13 @@ mixin post(post, truncate, manage=false, globalmanage=false) if !post.thread if post.sticky img(src='/img/sticky.svg' height='12') + | if post.saged img(src='/img/saged.svg' height='12') + | if post.locked img(src='/img/locked.svg' height='12') - | + | if post.subject span.post-subject #{post.subject} | diff --git a/views/pages/catalog.pug b/views/pages/catalog.pug index 945732bb..be09ee56 100644 --- a/views/pages/catalog.pug +++ b/views/pages/catalog.pug @@ -7,6 +7,8 @@ block head block content include ../includes/boardheader.pug br + include ../includes/postform.pug + br nav.pages#top a(href='#bottom') [Bottom] | diff --git a/wipe.js b/wipe.js index e35fe808..2037bdb2 100644 --- a/wipe.js +++ b/wipe.js @@ -42,7 +42,7 @@ const Mongo = require(__dirname+'/db/db.js') moderators: [], banners: [], settings: { - captcha: false, + captcha: true, forceAnon: true, ids: true, threadLimit: 100, @@ -63,7 +63,7 @@ const Mongo = require(__dirname+'/db/db.js') moderators: [], banners: [], settings: { - captcha: true, + captcha: false, forceAnon: false, ids: false, threadLimit: 100, From d178c28540d61669a535559fea9165c27a136dee Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 12:37:35 +0000 Subject: [PATCH 03/26] allow .mov and fix video resolution -- filter streams to only video --- helpers/files/file-check-mime-types.js | 1 + models/forms/make-post.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/helpers/files/file-check-mime-types.js b/helpers/files/file-check-mime-types.js index 2f1eedad..55aef447 100644 --- a/helpers/files/file-check-mime-types.js +++ b/helpers/files/file-check-mime-types.js @@ -13,6 +13,7 @@ const animatedImageMimeTypes = new Set([ ]); const videoMimeTypes = new Set([ + 'video/quicktime', 'video/mp4', 'video/webm', ]); diff --git a/models/forms/make-post.js b/models/forms/make-post.js index ca8e9d03..3619e6be 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -122,6 +122,7 @@ module.exports = async (req, res, next, numFiles) => { //video metadata await videoUpload(file, filename, 'img'); const videoData = await videoIdentify(filename); + videoData.streams = videoData.streams.filter(stream => stream.width != null); //filter to only video streams or something with a resolution processedFile.duration = videoData.format.duration; processedFile.durationString = new Date(videoData.format.duration*1000).toLocaleString('en-US', {hour12:false}).split(' ')[1].replace(/^00:/, ''); processedFile.geometry = {width: videoData.streams[0].coded_width, height: videoData.streams[0].coded_height} // object with width and height pixels @@ -131,7 +132,7 @@ module.exports = async (req, res, next, numFiles) => { await videoThumbnail(filename); break; default: - return next(err); + return next(new Error(`invalid file mime type: ${mainType}`)); } //delete the temp file From eeb9a06afa8ef6a7521b89f9b2e4ae9b97623d11 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 13:21:05 +0000 Subject: [PATCH 04/26] stupid firefox not copying formatting in white-space:pre css --- gulp/res/css/style.css | 13 +++++++------ views/mixins/catalogtile.pug | 2 +- views/mixins/post.pug | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css index 37bfa3b2..ca64072f 100644 --- a/gulp/res/css/style.css +++ b/gulp/res/css/style.css @@ -19,6 +19,11 @@ body { flex-direction: column; } +pre { + font-family: inherit; + margin: 1em 3em; +} + .code { text-align: left; border-left: 10px solid #B7C5D9; @@ -176,14 +181,10 @@ object { color: green; } -blockquote a, a:hover { +pre a, a:hover { color: #d00!important; } -blockquote { - white-space: pre-wrap; -} - .thread, .action-wrapper, .form-wrapper, .table-container { display: flex; flex-direction: column; @@ -545,7 +546,7 @@ hr { width: 100%; } - blockquote { + pre { margin: 1em; } diff --git a/views/mixins/catalogtile.pug b/views/mixins/catalogtile.pug index 36379e9a..02955d44 100644 --- a/views/mixins/catalogtile.pug +++ b/views/mixins/catalogtile.pug @@ -25,4 +25,4 @@ mixin catalogtile(board, post, truncate) span: a.no-decoration.post-subject(href=postURL) #{post.subject || 'No Subject'} if post.message br - blockquote.no-m-p.post-message !{post.message} + pre.no-m-p.post-message !{post.message} diff --git a/views/mixins/post.pug b/views/mixins/post.pug index 7286708d..283047b6 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -70,11 +70,11 @@ mixin post(post, truncate, manage=false, globalmanage=false) truncatedMessage = splitPost.slice(0, 10).join('\n'); truncated = true; } - blockquote.post-message !{truncatedMessage} + pre.post-message !{truncatedMessage} if truncated blockquote.left.clear-both Message too long. #[a(href=postURL) View the full text] else - blockquote.post-message !{post.message} + pre.post-message !{post.message} if post.banmessage blockquote.left.clear-both.banmessage USER WAS BANNED FOR THIS POST (#{post.banmessage}) if post.omittedposts || post.omittedimages From 469a40d4c39b7953db2f5ae6bb2d03d341aa02ed Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 13:43:08 +0000 Subject: [PATCH 05/26] spoiler spoilers filename (keeps title and download so hovering can reveal filename) --- views/mixins/post.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/mixins/post.pug b/views/mixins/post.pug index 283047b6..64df963e 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -44,7 +44,7 @@ mixin post(post, truncate, manage=false, globalmanage=false) each file in post.files .post-file span.post-file-info - span: a(href='/img/'+file.filename title=file.originalFilename download=file.originalFilename) #{file.originalFilename} + span: a(href='/img/'+file.filename title=file.originalFilename download=file.originalFilename) #{post.spoiler ? 'Spoiler File' : file.originalFilename} br span | (#{file.sizeString}, #{file.geometryString} From 68c5e520f64f3315f48e62a5b46d273dab15b257 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 18:48:10 +0000 Subject: [PATCH 06/26] fix truncate for long posts vs high line count --- views/mixins/post.pug | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/mixins/post.pug b/views/mixins/post.pug index 64df963e..6de00dcb 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -66,9 +66,12 @@ mixin post(post, truncate, manage=false, globalmanage=false) const messageLines = splitPost.length; let truncatedMessage = post.message; let truncated = false; - if (messageLines > 10 || post.message.length > 1000) { + if (messageLines > 10) { truncatedMessage = splitPost.slice(0, 10).join('\n'); truncated = true; + } else if (post.message.length > 1000) { + truncatedMessage = `${post.message.substring(0,1000)}...`; + truncated = true; } pre.post-message !{truncatedMessage} if truncated From 263a937081f7b2ece40c57661bc05afdd9c4cc72 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 26 May 2019 18:50:23 +0000 Subject: [PATCH 07/26] properly rebuild on bans, sagdes, locks, etc --- models/forms/actionhandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/forms/actionhandler.js b/models/forms/actionhandler.js index 3a6b8a54..78dd0360 100644 --- a/models/forms/actionhandler.js +++ b/models/forms/actionhandler.js @@ -291,9 +291,9 @@ module.exports = async (req, res, next) => { //rebuild current and older pages for deletes parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, afterPages)); } else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant - //rebuild current and newer pages for stickies + //rebuild current and newer pages parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, threadPageOldest)); - } else if ((res.locals.hasPerms && (req.body.lock || req.body.sage)) || req.body.spoiler) { + } else if (req.body.lock || req.body.sage || req.body.spoiler || req.body.ban || req.body.global_ban) { //rebuild inbewteen pages for things that dont cause page/thread movement //should rebuild only affected pages, but finding the page of all affected //threads could end up being slower/more resource intensive. this is simpler. From 8b327111e1400a30904b03691baac307102611df Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:11:36 +0000 Subject: [PATCH 08/26] shorten line --- helpers/captchaverify.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helpers/captchaverify.js b/helpers/captchaverify.js index 21ee4de9..ec939409 100644 --- a/helpers/captchaverify.js +++ b/helpers/captchaverify.js @@ -8,7 +8,9 @@ const Captchas = require(__dirname+'/../db/captchas.js') module.exports = async (req, res, next) => { //skip captcha if disabled on board for posts only - if (res.locals.board && req.path === `/board/${res.locals.board._id}/post` && !res.locals.board.settings.captcha) { + if (res.locals.board + && req.path === `/board/${res.locals.board._id}/post` + && !res.locals.board.settings.captcha) { return next(); } From 1a186b865c3353f5000b2522d2f1f04bd07378ec Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:12:30 +0000 Subject: [PATCH 09/26] dont delete files since we hash them. keep for later --- db/posts.js | 11 +-- models/forms/actionhandler.js | 76 ++++++++++---------- models/forms/delete-post.js | 8 +-- models/forms/deletebanners.js | 2 - models/forms/deletepostsfiles.js | 40 ++++++----- views/includes/actionfooter.pug | 9 ++- views/includes/actionfooter_globalmanage.pug | 4 +- views/includes/actionfooter_manage.pug | 4 +- 8 files changed, 71 insertions(+), 83 deletions(-) diff --git a/db/posts.js b/db/posts.js index 15b9f787..83d06d44 100644 --- a/db/posts.js +++ b/db/posts.js @@ -2,7 +2,6 @@ const Mongo = require(__dirname+'/db.js') , Boards = require(__dirname+'/boards.js') - , deletePostFiles = require(__dirname+'/../helpers/files/deletepostfiles.js') , db = Mongo.client.db('jschan').collection('posts'); module.exports = { @@ -272,7 +271,7 @@ module.exports = { }, insertOne: async (board, data, thread) => { - if (data.thread !== null && data.email !== 'sage' && !thread.saged) { + if (data.thread !== null) { const filter = { 'postId': data.thread, 'board': board @@ -355,14 +354,6 @@ module.exports = { const threadPosts = await module.exports.getMultipleThreadPosts(board, threadIds); //combine them const postsAndThreads = threads.concat(threadPosts); - //get the filenames and delete all the files - let fileNames = []; - postsAndThreads.forEach(post => { - fileNames = fileNames.concat(post.files.map(x => x.filename)) - }); - if (fileNames.length > 0) { - await deletePostFiles(fileNames); - } //get the mongoIds and delete them all const postMongoIds = postsAndThreads.map(post => Mongo.ObjectId(post._id)); await module.exports.deleteMany(postMongoIds); diff --git a/models/forms/actionhandler.js b/models/forms/actionhandler.js index 78dd0360..cba84eab 100644 --- a/models/forms/actionhandler.js +++ b/models/forms/actionhandler.js @@ -49,23 +49,21 @@ module.exports = async (req, res, next) => { const passwordCombinedQuery = {}; let aggregateNeeded = false; try { - if (res.locals.hasPerms) { - // if getting global banned, board ban doesnt matter - if (req.body.global_ban) { - const { message, action, query } = await banPoster(req, res, next, null, res.locals.posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); - } else if (req.body.ban) { - const { message, action, query } = await banPoster(req, res, next, req.params.board, res.locals.posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); + // if getting global banned, board ban doesnt matter + if (req.body.global_ban) { + const { message, action, query } = await banPoster(req, res, next, null, res.locals.posts); + if (action) { + combinedQuery[action] = { ...combinedQuery[action], ...query} } + messages.push(message); + } else if (req.body.ban) { + const { message, action, query } = await banPoster(req, res, next, req.params.board, res.locals.posts); + if (action) { + combinedQuery[action] = { ...combinedQuery[action], ...query} + } + messages.push(message); } - if (res.locals.hasPerms && (req.body.delete_ip_board || req.body.delete_ip_global)) { + if (req.body.delete_ip_board || req.body.delete_ip_global) { const deletePostIps = res.locals.posts.map(x => x.ip); let query = { 'ip': { @@ -88,8 +86,8 @@ module.exports = async (req, res, next) => { aggregateNeeded = true; } else { // if it was getting deleted, we cant do any of these - if (req.body.delete_file) { - const { message, action, query } = await deletePostsFiles(passwordPosts); + if (req.body.delete_file || req.body.unlink_file) { + const { message, action, query } = await deletePostsFiles(passwordPosts, req.body.unlink_file); if (action) { aggregateNeeded = true; passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query} @@ -102,29 +100,27 @@ module.exports = async (req, res, next) => { } messages.push(message); } - if (res.locals.hasPerms) { - //lock, sticky, sage - if (req.body.sage) { - const { message, action, query } = sagePosts(res.locals.posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); + //lock, sticky, sage + if (req.body.sage) { + const { message, action, query } = sagePosts(res.locals.posts); + if (action) { + combinedQuery[action] = { ...combinedQuery[action], ...query} } - if (req.body.lock) { - const { message, action, query } = lockPosts(res.locals.posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); + messages.push(message); + } + if (req.body.lock) { + const { message, action, query } = lockPosts(res.locals.posts); + if (action) { + combinedQuery[action] = { ...combinedQuery[action], ...query} } - if (req.body.sticky) { - const { message, action, query } = stickyPosts(res.locals.posts); - if (action) { - combinedQuery[action] = { ...combinedQuery[action], ...query} - } - messages.push(message); + messages.push(message); + } + if (req.body.sticky) { + const { message, action, query } = stickyPosts(res.locals.posts); + if (action) { + combinedQuery[action] = { ...combinedQuery[action], ...query} } + messages.push(message); } // cannot report and dismiss at same time if (req.body.report) { @@ -133,7 +129,7 @@ module.exports = async (req, res, next) => { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); - } else if (res.locals.hasPerms && req.body.dismiss) { + } else if (req.body.dismiss) { const { message, action, query } = dismissReports(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} @@ -147,7 +143,7 @@ module.exports = async (req, res, next) => { combinedQuery[action] = { ...combinedQuery[action], ...query} } messages.push(message); - } else if (res.locals.hasPerms && req.body.global_dismiss) { + } else if (req.body.global_dismiss) { const { message, action, query } = dismissGlobalReports(res.locals.posts); if (action) { combinedQuery[action] = { ...combinedQuery[action], ...query} @@ -293,7 +289,7 @@ module.exports = async (req, res, next) => { } else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant //rebuild current and newer pages parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, threadPageOldest)); - } else if (req.body.lock || req.body.sage || req.body.spoiler || req.body.ban || req.body.global_ban) { + } else if (req.body.lock || req.body.sage || req.body.spoiler || req.body.ban || req.body.global_ban || req.body.unlink_file) { //rebuild inbewteen pages for things that dont cause page/thread movement //should rebuild only affected pages, but finding the page of all affected //threads could end up being slower/more resource intensive. this is simpler. diff --git a/models/forms/delete-post.js b/models/forms/delete-post.js index eee05592..04755730 100644 --- a/models/forms/delete-post.js +++ b/models/forms/delete-post.js @@ -1,7 +1,6 @@ 'use strict'; const uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') - , deletePostFiles = require(__dirname+'/../../helpers/files/deletepostfiles.js') , remove = require('fs-extra').remove , Mongo = require(__dirname+'/../../db/db.js') , Posts = require(__dirname+'/../../db/posts.js'); @@ -39,7 +38,7 @@ module.exports = async (req, res, next, posts, board) => { //combine them all into one array, there may be duplicates but it shouldnt matter const allPosts = posts.concat(threadPosts) - //delete posts from DB + //get all mongoids and delete posts from const postMongoIds = allPosts.map(post => Mongo.ObjectId(post._id)) const deletedPosts = await Posts.deleteMany(postMongoIds).then(result => result.deletedCount); @@ -49,11 +48,6 @@ module.exports = async (req, res, next, posts, board) => { fileNames = fileNames.concat(post.files.map(x => x.filename)) }) - //delete post files - if (fileNames.length > 0) { - await deletePostFiles(fileNames); - } - //hooray! return { message:`Deleted ${threads.length} threads and ${deletedPosts-threads.length} posts` }; diff --git a/models/forms/deletebanners.js b/models/forms/deletebanners.js index f8015408..84224289 100644 --- a/models/forms/deletebanners.js +++ b/models/forms/deletebanners.js @@ -12,8 +12,6 @@ module.exports = async (req, res, next) => { remove(`${uploadDirectory}banner/${filename}`); })); - // i dont think there is a way to get the number of array items removed with $pullAll - // so i cant return how many banners were deleted await Boards.removeBanners(req.params.board, req.body.checkedbanners); return res.render('message', { diff --git a/models/forms/deletepostsfiles.js b/models/forms/deletepostsfiles.js index b1d80988..beeb509d 100644 --- a/models/forms/deletepostsfiles.js +++ b/models/forms/deletepostsfiles.js @@ -3,7 +3,7 @@ const remove = require('fs-extra').remove , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') -module.exports = async (posts) => { +module.exports = async (posts, unlinkOnly) => { //get filenames from all the posts let fileNames = []; @@ -13,25 +13,31 @@ module.exports = async (posts) => { if (fileNames.length === 0) { return { - message: 'No files to delete' + message: 'No files found' } } - //delete all the files using the filenames - await Promise.all(fileNames.map(async filename => { - //dont question it. - return Promise.all([ - remove(`${uploadDirectory}img/${filename}`), - remove(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.png`) - ]) - })); + if (unlinkOnly) { + return { + message:`Unlinked ${fileNames.length} file(s) across ${posts.length} post(s)`, + action:'$set', + query: { + 'files': [] + } + }; + } else { + //delete all the files using the filenames + await Promise.all(fileNames.map(async filename => { + //dont question it. + return Promise.all([ + remove(`${uploadDirectory}img/${filename}`), + remove(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.jpg`) + ]) + })); + return { + message:`Deleted ${fileNames.length} file(s) from server`, + }; + } - return { - message:`Deleted ${fileNames.length} file(s) across ${posts.length} post(s)`, - action:'$set', - query: { - 'files': [] - } - }; } diff --git a/views/includes/actionfooter.pug b/views/includes/actionfooter.pug index 5e938d6a..54c3997c 100644 --- a/views/includes/actionfooter.pug +++ b/views/includes/actionfooter.pug @@ -6,11 +6,11 @@ details.toggle-label input.post-check(type='checkbox', name='delete' value=1) | Delete label - input.post-check(type='checkbox', name='delete_file' value=1) - | Delete File Only + input.post-check(type='checkbox', name='unlink_file' value=1) + | Unlink Files label input.post-check(type='checkbox', name='spoiler' value=1) - | Spoiler Images + | Spoiler Files label input#password(type='text', name='password', placeholder='post password' autocomplete='off') label @@ -29,6 +29,9 @@ details.toggle-label label input.post-check(type='checkbox', name='delete_ip_global' value=1) | Delete from IP globally + label + input.post-check(type='checkbox', name='delete_file' value=1) + | Delete Files label input.post-check(type='checkbox', name='sticky' value=1) | Sticky diff --git a/views/includes/actionfooter_globalmanage.pug b/views/includes/actionfooter_globalmanage.pug index 90d9b47a..3d70d36d 100644 --- a/views/includes/actionfooter_globalmanage.pug +++ b/views/includes/actionfooter_globalmanage.pug @@ -7,10 +7,10 @@ details.toggle-label | Delete label input.post-check(type='checkbox', name='delete_file' value=1) - | Delete File Only + | Delete Files label input.post-check(type='checkbox', name='spoiler' value=1) - | Spoiler Images + | Spoiler Files label input.post-check(type='checkbox', name='delete_ip_global' value=1) | Delete from IP globally diff --git a/views/includes/actionfooter_manage.pug b/views/includes/actionfooter_manage.pug index 636e708c..d36f9770 100644 --- a/views/includes/actionfooter_manage.pug +++ b/views/includes/actionfooter_manage.pug @@ -7,10 +7,10 @@ details.toggle-label | Delete label input.post-check(type='checkbox', name='delete_file' value=1) - | Delete File Only + | Delete Files label input.post-check(type='checkbox', name='spoiler' value=1) - | Spoiler Images + | Spoiler Files label input.post-check(type='checkbox', name='global_report' value=1) | Global Report From 415724779de1babb8f8d1b426fd16f41cdcccd19 Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:13:18 +0000 Subject: [PATCH 10/26] catalog tile subject above reply/image count --- views/mixins/catalogtile.pug | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/mixins/catalogtile.pug b/views/mixins/catalogtile.pug index 02955d44..0c59d1a8 100644 --- a/views/mixins/catalogtile.pug +++ b/views/mixins/catalogtile.pug @@ -18,11 +18,11 @@ mixin catalogtile(board, post, truncate) if post.locked img(src='/img/locked.svg' height='12') | + span: a.no-decoration.post-subject(href=postURL) #{post.subject || 'No Subject'} + br span Replies: #{post.replyposts} | span Files: #{post.replyfiles} - br - span: a.no-decoration.post-subject(href=postURL) #{post.subject || 'No Subject'} if post.message br pre.no-m-p.post-message !{post.message} From a913b8290b1505980a8833c0eb14662b4ecbc819 Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:17:24 +0000 Subject: [PATCH 11/26] board specific banners folder --- models/forms/uploadbanners.js | 30 ++++++++++++++---------------- models/pages/banners.js | 2 +- views/pages/thread.pug | 1 - 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/models/forms/uploadbanners.js b/models/forms/uploadbanners.js index 980b31ba..5dd8fb66 100644 --- a/models/forms/uploadbanners.js +++ b/models/forms/uploadbanners.js @@ -1,13 +1,12 @@ 'use strict'; -const uuidv4 = require('uuid/v4') - , path = require('path') +const path = require('path') , remove = require('fs-extra').remove , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , imageUpload = require(__dirname+'/../../helpers/files/imageupload.js') , fileCheckMimeType = require(__dirname+'/../../helpers/files/file-check-mime-types.js') - , deleteFailedFiles = require(__dirname+'/../../helpers/files/deletefailed.js') , imageIdentify = require(__dirname+'/../../helpers/files/image-identify.js') + , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js') , Boards = require(__dirname+'/../../db/boards.js') module.exports = async (req, res, next, numFiles) => { @@ -17,6 +16,7 @@ module.exports = async (req, res, next, numFiles) => { // check all mime types befoer we try saving anything for (let i = 0; i < numFiles; i++) { if (!fileCheckMimeType(req.files.file[i].mimetype, {image: true, animatedImage: true, video: false})) { + await deleteTempFiles(req.files.file) return res.status(400).render('message', { 'title': 'Bad request', 'message': `Invalid file type for ${req.files.file[i].name}. Mimetype ${req.files.file[i].mimetype} not allowed.`, @@ -26,37 +26,35 @@ module.exports = async (req, res, next, numFiles) => { } const filenames = []; - // then upload for (let i = 0; i < numFiles; i++) { const file = req.files.file[i]; - const uuid = uuidv4(); - const filename = uuid + path.extname(file.name); + const filename = file.sha256 + path.extname(file.name); file.filename = filename; //for error to delete failed files filenames.push(filename); - //upload it - await imageUpload(file, filename, 'banner'); - const imageData = await imageIdentify(filename, 'banner'); +//todo: CHECK FILE HASHES before uploading, if exists skip identifying and uploading + //upload and get metadata + const imageData = await imageIdentify(req.files.file[i].tempFilePath, null, true); const geometry = imageData.size; - await remove(file.tempFilePath); //make sure its 300x100 banner if (geometry.width !== 300 || geometry.height !== 100) { - const fileNames = []; - for (let i = 0; i < req.files.file.length; i++) { - remove(req.files.file[i].tempFilePath).catch(e => console.error); - fileNames.push(req.files.file[i].filename); - } - deleteFailedFiles(fileNames, 'banner').catch(e => console.error); + await deleteTempFiles(req.files.file) return res.status(400).render('message', { 'title': 'Bad request', 'message': `Invalid file ${file.name}. Banners must be 300x100.`, 'redirect': redirect }); } + await imageUpload(file, filename, `banner/${req.params.board}`); +//end todo + + await remove(file.tempFilePath); + } await Boards.addBanners(req.params.board, filenames); +//TODO: banners pages // await buildBanners(res.locals.board); return res.render('message', { diff --git a/models/pages/banners.js b/models/pages/banners.js index dbd6b309..c3ff2e5f 100644 --- a/models/pages/banners.js +++ b/models/pages/banners.js @@ -22,7 +22,7 @@ module.exports = async (req, res, next) => { if (board.banners.length > 0) { const randomBanner = board.banners[Math.floor(Math.random()*board.banners.length)]; - return res.redirect(`/banner/${randomBanner}`); + return res.redirect(`/banner/${req.query.board}/${randomBanner}`); } return res.redirect('/img/defaultbanner.png'); diff --git a/views/pages/thread.pug b/views/pages/thread.pug index 035c26c3..998ed669 100644 --- a/views/pages/thread.pug +++ b/views/pages/thread.pug @@ -22,7 +22,6 @@ block content a(href=`/${board._id}/catalog.html`) [Catalog] hr(size=1) form(action=`/forms/board/${board._id}/actions` method='POST' enctype='application/x-www-form-urlencoded') - input(type='hidden' name='_csrf' value=csrf) section.thread +post(thread) for post in thread.replies From a50797cb1a3ff67dd31b0d827c42cef0054a91bd Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:17:54 +0000 Subject: [PATCH 12/26] identify based on temp file --- helpers/files/image-identify.js | 4 ++-- helpers/files/video-identify.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helpers/files/image-identify.js b/helpers/files/image-identify.js index 709c118d..f42789b3 100644 --- a/helpers/files/image-identify.js +++ b/helpers/files/image-identify.js @@ -2,10 +2,10 @@ const gm = require('@tohru/gm') , configs = require(__dirname+'/../../configs/main.json') , uploadDirectory = require(__dirname+'/../uploadDirectory.js'); -module.exports = (filename, folder) => { +module.exports = (filename, folder, temp) => { return new Promise((resolve, reject) => { - gm(`${uploadDirectory}${folder}/${filename}`) + gm(temp === true ? filename : `${uploadDirectory}${folder}/${filename}`) .identify(function (err, data) { if (err) { return reject(err); diff --git a/helpers/files/video-identify.js b/helpers/files/video-identify.js index dd98a735..a4ebbe57 100644 --- a/helpers/files/video-identify.js +++ b/helpers/files/video-identify.js @@ -2,10 +2,10 @@ const ffmpeg = require('fluent-ffmpeg') , configs = require(__dirname+'/../../configs/main.json') , uploadDirectory = require(__dirname+'/../uploadDirectory.js'); -module.exports = (filename) => { +module.exports = (filename, folder, temp) => { return new Promise((resolve, reject) => { - ffmpeg.ffprobe(`${uploadDirectory}img/${filename}`, (err, metadata) => { + ffmpeg.ffprobe(temp === true ? filename : `${uploadDirectory}${folder}/${filename}`, (err, metadata) => { if (err) { return reject(err) } From 6b549c1539d52a5fac1a73d14aff50161a21095f Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:18:45 +0000 Subject: [PATCH 13/26] floating postform and 'deleted.png' for deleted files nested in object for clean hash file deletion --- gulp/res/css/style.css | 55 ++++++++++++++++-------- gulp/res/img/deleted.png | Bin 0 -> 3022 bytes views/includes/postform.pug | 81 ++++++++++++++++++------------------ views/mixins/post.pug | 19 ++++++--- views/pages/manage.pug | 2 +- 5 files changed, 92 insertions(+), 65 deletions(-) create mode 100644 gulp/res/img/deleted.png diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css index ca64072f..b4c2175a 100644 --- a/gulp/res/css/style.css +++ b/gulp/res/css/style.css @@ -21,7 +21,13 @@ body { pre { font-family: inherit; - margin: 1em 3em; + margin: 1em 2em; + white-space: pre-wrap; +} + +.replies { + font-size: smaller; + clear: both; } .code { @@ -32,7 +38,6 @@ pre { font-family: monospace; margin: 0.5em 0; display: flex; - clear: both; overflow-x: auto; white-space: pre; } @@ -93,17 +98,6 @@ object { background: #d6daf0; } -.ecatalog-tile-button { - width: 100%; - line-height: 30px; - float: left; - background: #B7C5D9; - text-decoration: none; - color: black; - margin-bottom: 5px; - overflow: hidden; -} - .catalog-tile { padding: 5px; margin: 2px; @@ -161,6 +155,13 @@ object { font-weight: bold; } +.close { + text-decoration: none; + width: 1.75em; + justify-content: center; + font-weight: bolder; +} + .reports { background: #fca!important; border-color: #c97!important; @@ -390,8 +391,8 @@ input textarea { } .post-container:target { - background-color: #d6bad0; - border-color: #ba9dbf; + background-color: #d6bad0 !important; + border: 1px solid #ba9dbf !important; } .post-container.op { @@ -484,6 +485,24 @@ input[type="file"] { background: white; } +#postform { + display: none; + width: 400px; + max-width: calc(100% - 10px); + position: fixed; + top: 45px; + right: 5px; + background-color: #D6DAF0; + border: 1px solid #b7c5d9; + padding: 5px; + z-index: 1; + box-sizing: border-box; +} + +#postform:target { + display: flex; +} + .postform-row, .postform-col { display: flex; } @@ -539,7 +558,7 @@ hr { } .form-post { - width: 100%; + width: calc(100% - 10px)!important; } .form-login { @@ -573,8 +592,8 @@ hr { width: 100%; } - .post-info { - /*background-color: #B7C5D9;*/ + #postform { + top: 5px!important; } } diff --git a/gulp/res/img/deleted.png b/gulp/res/img/deleted.png new file mode 100644 index 0000000000000000000000000000000000000000..848c79c051a81825e0bfdb0f9c0ac3791ae8d161 GIT binary patch literal 3022 zcmZvec{J4BAII-37^7iI#UqWiU6!QCUe;uJ5}Gh$Ejvje)A){JQjN({*(W6_Yhpr- zc?xA2LPkhN7%9utw3sLK8~^^!bMEpwpL0Lw-p{%BeeQXux;WWl&`M|k0LI?V>bRKD z+CWK)=a*)_U1CW3IoMi(KWpu7^TSL4WG>rVS)Pa+nJ1w?>~?|PbwDn882oJ-7m5{skng9HFL62 z=XZSGyIKBrZA_crxgm`W_HTOv8)Pg>_+?1T?R=RlY*SEUK$Jk$=n=8V1>iGCGC$(e zMuK4thYcT>>X4Y%1Sa3K~}vJQDT5c^)WI7)uD2FmOiR zqAVa&EMUShgzAu5+$2Wy6|Fn&4eV+v9LZm6lG&j=W?e%_`g3segYm7B%j{6TjN({~ z3gktxuoSkzc;`_Y4TxGmbNGXHOMOR0a{+1Pyy$FkyQYgDOtm?7yWn`HAnbA+fTK+O zSte`nWqFBFoTsA#2Gs=YOwKo$>fjYNf}cr%K__J3^layg(%?7-WfEXEYXIyLkMQ(_ z_87>Pj$3chUZFN?dSS8Uj~PsmbEYsSHLFBz(6Nc1q69vExf60aT=-6|nKMv?2{4c` zXawa_0y*&IJ#ECRpkTmg+w3h5f@N&%L9%FC0iW)>8JL=y#_hgN%$*x=QE_y1eBIS` z?m&%}>XI?^_Npz61n2Dq&tS_q_MMRvrN_FZAQsE4udh2}K2~C8db+5(8aom7PV`9@ zcWmREq|LA(xF8a&2Xe4ExPUF%$+^|O{EDtJ$>-#$-`C3u2aS!5Sah-rk;v%#cV%TI zC?dkf+FAmQM!UGXr_7H&o17bdnw_7Y-%P=vfR7rK2tT=w*8)~UHp)Y%!6twPS+IAq zJT0i>#!tUD&IDX%8qT1*;?W~%kw|p))uiIqt=2m?9v1h=$f}A{G&=o$bMsa=H@BTy zTCM#L9M;op^mKK7a50~)eTYP&yMn^77n0Bvmp&1d4eA5CHZyB$tBZ;#ZEbBx_So~- zbJ5ZJ?#sNKy3S^;(|)mfuj2RfERg~%r7qADFhLkNSSidnTn$_}e?DhXOFAhGw&c<8{o+q;{I84~3r_e%EKeyMKco!0 zKh20{^gAnBS{R@{sP7&=_|&7p!7>kNNL+r?`7hiX;eJmPXL=br3ZQ^3ndwpV>-%>s z7^)&N$W$fdFkCmQcJ=0g#cHx=XpJMCNm@|`$9_DG@NRE-fM6f3__;9V07zfk6fn<* zt9_50enxqIVg9hCrRB(CBa?YxzkxyE*Ll=h0)SFdBwtSd`0*qAU?eVLoJC7dU!QgR zwo+`?^xk`w_lkSz-?M9jFLxaHVdteeX7iETuX%5lOt{!Vh&T{(ezF|{*xU+F@i|ZY zsT0;ST7bMXK{dJ_Jk9#?d$C0dcs0gI4^?{Wd+kt`)xgokcZ78|Uy0%VwtL9PFw+_E z?=QIny!3~)*Qv_-U?IGf>xyAk-mOzBOOtc}O?TbdQc1+)gAEfmgG-k#;c19~$5c5L zcy@_#fsL&#N1@Opc$T9aTNkWXLZ70J#4KFhFo`Z{m|c0t;n-As5AW{za9WtW(t9^q zMgb1AiL7<=BQI;M)z?otY30fRm5>2*%~!>aI{ph^7wvLcXTA>%2k$+YSJ!VFIr+8T zmm!B!q(kCo?%2UVf^`@S{tG)rG_HC!R|->0l$%ON*+oS~j}k@)9y*H^Mbc1xU8%pa@@@3{@>c3 z%p2gOn_Jt&#Do#YS;A*8n`R&G)?ca&xIvc0%HeRzOG|yoWHSc`2fVB6OAXt!sDJ=X4WTQ{E?fv(mE@_w3K2_j zX_oiM5C`l5e1z8ET_bpuX5d&%FCzP=E74s4pd;hb&%eqZKUS2b;t7NvIkP49sfzuk zaRR8S`KOXc6@jz7Owz{JTLbY= z5&x~rVJurUfh^day=aMQfK-~P;a~lcwjqqQv*AMRK?nY6bgQ*PBwZl9d zXb55@Ve1hV+Idxq$Q}ewO1LRW>@t=ESn60qDM*Fm0pMqV;Hk)r$~Ido${#fci(0N< zR@Z!%Ci|;<==@yvvPtIXBi9U|sEAs<)TEGGo2a5f>vSeer86F<$%^8?g!kg4ffhl_ z{A`~s`EgV9_R{j&{1@d4RsrQ5VT8N<3_m^I^quMX@sD3Zl<#14%Kw-&mDoh|*Kmdc zg{CQKcE5w`x8coVN#9bNTnSp+7i$;WsxE&ZboqKpLC3sESDk)N*$=ztIkYGHb|y$a zy=4m8+h2!|zBT*iy6xMs>Tu5&h%GfzV3%x?moCvm>$W^U(+OBK%2{gXRCPI`l=m@W z(4gNG72QYjj{N}YG5?C{2DMkOwqknm;58DNZs$X;$%ohyLi>Ya zAyGysY-j$L8U)3GpBVwOI7MaC#UY$>T^zzKPRmgkoS|D7q;{TjBL?IcA$Mn2?U6?z zvz@O2i=mzZeh2G!Wwox!ebBa9`0wptc`Bq(*n{xbb&-$N+cIXdGLoL2_k%~_e8+2fA?POJX H*eB_K#|nk6 literal 0 HcmV?d00001 diff --git a/views/includes/postform.pug b/views/includes/postform.pug index 38107809..33d76092 100644 --- a/views/includes/postform.pug +++ b/views/includes/postform.pug @@ -1,47 +1,46 @@ section.form-wrapper.flex-center - details.toggle-label - summary.toggle-summary Show Post Form - form.form-post(action=`/forms/board/${board._id}/post`, enctype='multipart/form-data', method='POST') - //input(type='hidden' name='_csrf' value=csrf) - input(type='hidden' name='thread' value=thread != null ? thread.postId : null) - unless board.settings.forceAnon - section.postform-row - .postform-label Name - input#name(type='text', name='name', placeholder=board.defaultName autocomplete='off' maxlength='50') + a.toggle-summary(href='#postform') Show Post Form + form.form-post#postform(action=`/forms/board/${board._id}/post`, enctype='multipart/form-data', method='POST') + input(type='hidden' name='thread' value=thread != null ? thread.postId : null) + unless board.settings.forceAnon + section.postform-row + .postform-label Name + input#name(type='text', name='name', placeholder=board.defaultName autocomplete='off' maxlength='50') + a.close.postform-style.ml-1(href='#!') X + section.postform-row + .postform-label Subject + input#title(type='text', name='subject', autocomplete='off' maxlength='50') + section.postform-row + .postform-label Email + input#name(type='text', name='email', autocomplete='off' maxlength='50') + else + section.postform-row + .postform-label Sage + label.postform-style.ph-5 + input#spoiler(type='checkbox', name='email', value='sage') + | Sage + a.close.postform-style.ml-1(href='#!') X + if !thread section.postform-row .postform-label Subject input#title(type='text', name='subject', autocomplete='off' maxlength='50') - section.postform-row - .postform-label Email - input#name(type='text', name='email', autocomplete='off' maxlength='50') - else - section.postform-row - .postform-label Sage - label.postform-style.ph-5 - input#spoiler(type='checkbox', name='email', value='sage') - | Sage - if !thread - section.postform-row - .postform-label Subject - input#title(type='text', name='subject', autocomplete='off' maxlength='50') + section.postform-row + .postform-label Message + textarea#message(name='message', rows='5', autocomplete='off' maxlength='2000') + if board.settings.maxFiles !== 0 section.postform-row - .postform-label Message - textarea#message(name='message', rows='5', autocomplete='off' maxlength='2000') - if board.settings.maxFiles !== 0 - section.postform-row - .postform-label Files - input#file(type='file', name='file' multiple='multiple') - label.postform-style.ph-5.ml-1 - input#spoiler(type='checkbox', name='spoiler', value='true') - | Spoiler + .postform-label Files + input#file(type='file', name='file' multiple='multiple') + label.postform-style.ph-5.ml-1 + input#spoiler(type='checkbox', name='spoiler', value='true') + | Spoiler + section.postform-row + .postform-label Password + input#password(type='password', name='password', autocomplete='off' placeholder='password for deleting post later' maxlength='50') + if board.settings.captcha section.postform-row - .postform-label Password - input#password(type='password', name='password', autocomplete='off' placeholder='password for deleting post later' maxlength='50') - if board.settings.captcha - section.postform-row - .postform-label Captcha - .postform-col - img.captcha(src='/captcha' width=200 height=80) - input#captcha(type='text', name='captcha', autocomplete='off' placeholder='captcha text' maxlength='6') - input(type='submit', value=`New ${threads ? 'Thread' : 'Reply'}`) - //.mode Posting mode: #{threads ? 'Thread' : 'Reply'} + .postform-label Captcha + .postform-col + img.captcha(src='/captcha' width=200 height=80) + input#captcha(type='text', name='captcha', autocomplete='off' placeholder='captcha text' maxlength='6') + input(type='submit', value=`New ${threads ? 'Thread' : 'Reply'}`) diff --git a/views/mixins/post.pug b/views/mixins/post.pug index 6de00dcb..4a6fb891 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -37,7 +37,10 @@ mixin post(post, truncate, manage=false, globalmanage=false) if post.userId span.user-id(style=`background: #${post.userId}`) #{post.userId} | - span: a(href=postURL) No.#{post.postId} + span No.#[a(href=postURL) #{post.postId}] + if !post.thread + | + span: a(href=`/${post.board}/thread/${post.thread || post.postId}.html#postform`) [Reply] .post-data if post.files.length > 0 .post-files @@ -57,8 +60,10 @@ mixin post(post, truncate, manage=false, globalmanage=false) object.file-thumb(data='/img/spoiler.png' width='128' height='128') else if file.hasThumb object.file-thumb(data=`/img/thumb-${file.filename.split('.')[0]}.jpg`) + img(src='/img/deleted.png') else object.file-thumb(data=`/img/${file.filename}`) + img(src='/img/deleted.png') if post.message if truncate - @@ -75,14 +80,18 @@ mixin post(post, truncate, manage=false, globalmanage=false) } pre.post-message !{truncatedMessage} if truncated - blockquote.left.clear-both Message too long. #[a(href=postURL) View the full text] + blockquote Message too long. #[a(href=postURL) View the full text] else pre.post-message !{post.message} if post.banmessage - blockquote.left.clear-both.banmessage USER WAS BANNED FOR THIS POST (#{post.banmessage}) + blockquote.banmessage USER WAS BANNED FOR THIS POST (#{post.banmessage}) if post.omittedposts || post.omittedimages - blockquote.left.clear-both #{post.omittedposts} post(s)#{post.omittedimages > 0 ? ' and '+post.omittedimages+' image(s)' : ''} omitted. #[a(href=postURL) View the full thread] - + blockquote #{post.omittedposts} post(s)#{post.omittedimages > 0 ? ' and '+post.omittedimages+' image(s)' : ''} omitted. #[a(href=postURL) View the full thread] + if post.backlinks && post.backlinks.length > 0 + .replies Replies: + each backlink in post.backlinks + a.quote(href=`/${post.board}/thread/${post.thread || post.postId}.html#${backlink}`) >>#{backlink} + | if manage === true each report in post.reports .reports.post-container diff --git a/views/pages/manage.pug b/views/pages/manage.pug index 43417353..116a3b97 100644 --- a/views/pages/manage.pug +++ b/views/pages/manage.pug @@ -72,7 +72,7 @@ block content each banner in board.banners label.banner-check input(type='checkbox' name='checkedbanners[]' value=banner) - object.board-banner(data=`/banner/${banner}` width='300' height='100') + object.board-banner(data=`/banner/${board._id}/${banner}` width='300' height='100') input(type='submit', value='delete') hr(size=1) h4.no-m-p Reports: From f23e4cd20e104ad0e480161f77f27a9591a091e4 Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 04:20:51 +0000 Subject: [PATCH 14/26] temp file delete helper, add unlink_file to actionchecker, backlinks in build -- might remove backlinking but works fine for now --- build.js | 86 ++++++++++++++++++++++++++++++++ controllers/forms.js | 28 ++++------- helpers/actionchecker.js | 3 +- helpers/files/deletetempfiles.js | 11 ++++ helpers/quotes.js | 10 ++-- models/forms/make-post.js | 55 +++++++++++++------- package-lock.json | 77 ++++++++++++++++++++++------ package.json | 1 + 8 files changed, 215 insertions(+), 56 deletions(-) create mode 100644 helpers/files/deletetempfiles.js diff --git a/build.js b/build.js index 2157aa2d..eb27cd53 100644 --- a/build.js +++ b/build.js @@ -28,6 +28,34 @@ module.exports = { if (!thread) { return; //this thread may have been an OP that was deleted } + + /* + temporary, jsut seeing how well this works + */ + const postMap = new Map() + postMap.set(thread.postId, thread) + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + postMap.set(reply.postId, reply); + } + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + if (!reply.quotes) continue; + for (let j = 0; j < reply.quotes.length; j++) { + const quote = reply.quotes[j]; + if (postMap.has(quote)) { + const post = postMap.get(quote) + if (!post.backlinks) { + post.backlinks = []; + } + post.backlinks.push(reply.postId); + } + } + } + /* + temporary, jsut seeing how well this works + */ + return render(`${board._id}/thread/${threadId}.html`, 'thread.pug', { board, thread @@ -40,6 +68,35 @@ module.exports = { if (!maxPage) { maxPage = Math.ceil((await Posts.getPages(board._id)) / 10); } + /* + temporary, jsut seeing how well this works + */ + for (let k = 0; k < threads.length; k++) { + const thread = threads[k]; + const postMap = new Map() + postMap.set(thread.postId, thread) + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + postMap.set(reply.postId, reply); + } + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + if (!reply.quotes) continue; + for (let j = 0; j < reply.quotes.length; j++) { + const quote = reply.quotes[j]; + if (postMap.has(quote)) { + const post = postMap.get(quote) + if (!post.backlinks) { + post.backlinks = []; + } + post.backlinks.push(reply.postId); + } + } + } + } + /* + temporary, jsut seeing how well this works + */ return render(`${board._id}/${page === 1 ? 'index' : page}.html`, 'board.pug', { board, threads, @@ -60,6 +117,35 @@ module.exports = { } const difference = endpage-startpage + 1; //+1 because for single pagemust be > 0 const threads = await Posts.getRecent(board._id, startpage, difference*10); + /* + temporary, jsut seeing how well this works + */ + for (let k = 0; k < threads.length; k++) { + const thread = threads[k]; + const postMap = new Map() + postMap.set(thread.postId, thread) + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + postMap.set(reply.postId, reply); + } + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + if (!reply.quotes) continue; + for (let j = 0; j < reply.quotes.length; j++) { + const quote = reply.quotes[j]; + if (postMap.has(quote)) { + const post = postMap.get(quote) + if (!post.backlinks) { + post.backlinks = []; + } + post.backlinks.push(reply.postId); + } + } + } + } + /* + temporary, jsut seeing how well this works + */ const buildArray = []; for (let i = startpage; i <= endpage; i++) { //console.log('multi building board page', `${board._id}/${i === 1 ? 'index' : i}.html`); diff --git a/controllers/forms.js b/controllers/forms.js index a57ec023..51ea54fb 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -13,15 +13,14 @@ 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') + , deleteTempFiles = require(__dirname+'/../helpers/files/deletetempfiles.js') , checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js') , checkPerms = require(__dirname+'/../helpers/hasperms.js') , paramConverter = require(__dirname+'/../helpers/paramconverter.js') , banCheck = require(__dirname+'/../helpers/bancheck.js') - , deletePostFiles = require(__dirname+'/../helpers/files/deletepostfiles.js') , verifyCaptcha = require(__dirname+'/../helpers/captchaverify.js') , actionHandler = require(__dirname+'/../models/forms/actionhandler.js') , csrf = require(__dirname+'/../helpers/csrfmiddleware.js') - , deleteFailedFiles = require(__dirname+'/../helpers/files/deletefailed.js') , actionChecker = require(__dirname+'/../helpers/actionchecker.js'); @@ -201,6 +200,9 @@ router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verif } if (errors.length > 0) { + if (numFiles > 0) { + await deleteTempFiles(req.files.file) + } return res.status(400).render('message', { 'title': 'Bad request', 'errors': errors, @@ -211,14 +213,8 @@ router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verif try { await makePost(req, res, next, numFiles); } catch (err) { - //handler errors here better if (numFiles > 0) { - const fileNames = [] - for (let i = 0; i < req.files.file.length; i++) { - remove(req.files.file[i].tempFilePath).catch(e => console.error); - fileNames.push(req.files.file[i].filename); - } - deletePostFiles(fileNames).catch(err => console.error); + await deleteTempFiles(req.files.file) } return next(err); } @@ -276,11 +272,14 @@ router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddlewar if (numFiles === 0) { errors.push('Must provide a file'); } - if (res.locals.board.banners.length > 100) { - errors.push('Limit of 100 banners reached'); + if (res.locals.board.banners.length+numFiles > 100) { + errors.push('Number of uploads would exceed 100 banner limit'); } if (errors.length > 0) { + if (numFiles > 0) { + await deleteTempFiles(req.files.file) + } return res.status(400).render('message', { 'title': 'Bad request', 'errors': errors, @@ -291,14 +290,9 @@ router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddlewar try { await uploadBanners(req, res, next, numFiles); } catch (err) { - const fileNames = []; if (numFiles > 0) { - for (let i = 0; i < req.files.file.length; i++) { - remove(req.files.file[i].tempFilePath).catch(e => console.error); - fileNames.push(req.files.file[i].filename); - } + await deleteTempFiles(req.files.file) } - deleteFailedFiles(fileNames, 'banner').catch(e => console.error); return next(err); } diff --git a/helpers/actionchecker.js b/helpers/actionchecker.js index be9231ac..ab43e884 100644 --- a/helpers/actionchecker.js +++ b/helpers/actionchecker.js @@ -1,7 +1,8 @@ 'use strict'; const actions = [ - {name:'delete_file', global:true, auth:false, passwords:true}, + {name:'unlink_file', global:true, auth:false, passwords:true}, + {name:'delete_file', global:true, auth:true, passwords:false}, {name:'spoiler', global:true, auth:false, passwords:true}, {name:'delete', global:true, auth:false, passwords:true}, {name:'lock', global:false, auth:true, passwords:false}, diff --git a/helpers/files/deletetempfiles.js b/helpers/files/deletetempfiles.js new file mode 100644 index 00000000..fe13a56b --- /dev/null +++ b/helpers/files/deletetempfiles.js @@ -0,0 +1,11 @@ +'use strict'; + +const remove = require('fs-extra').remove; + +module.exports = async (files) => { + + return Promise.all(files.map(async file => { + remove(file.tempFilePath); + })); + +} diff --git a/helpers/quotes.js b/helpers/quotes.js index 46fe8d46..92a1928b 100644 --- a/helpers/quotes.js +++ b/helpers/quotes.js @@ -11,14 +11,14 @@ module.exports = async (board, text) => { const quotes = text.match(quoteRegex); const crossQuotes = text.match(crossQuoteRegex); if (!quotes && !crossQuotes) { - return text; + return { quotedMessage: text, threadQuotes: [] }; } //make query for db including crossquotes const queryOrs = [] const crossQuoteMap = {}; if (quotes) { - const quoteIds = quotes.map(q => +q.substring(2)); + const quoteIds = [...new Set(quotes.map(q => +q.substring(2)))]; //only uniques queryOrs.push({ 'board': board, 'postId': { @@ -58,7 +58,7 @@ module.exports = async (board, text) => { const posts = await Posts.getPostsForQuotes(queryOrs); //if none of the quotes were real, dont do a replace if (posts.length === 0) { - return text; + return { quotedMessage: text, threadQuotes: [] }; } //turn the result into a map of postId => threadId/postId for (let i = 0; i < posts.length; i++) { @@ -71,10 +71,12 @@ module.exports = async (board, text) => { } //then replace the quotes with only ones that exist + const threadQuotes = []; if (quotes && Object.keys(postThreadIdMap).length > 0) { text = text.replace(quoteRegex, (match) => { const quotenum = +match.substring(2); if (postThreadIdMap[board] && postThreadIdMap[board][quotenum]) { + threadQuotes.push(quotenum) return `>>${quotenum}`; } return match; @@ -94,6 +96,6 @@ module.exports = async (board, text) => { }); } - return text; + return { quotedMessage: text, threadQuotes }; } diff --git a/models/forms/make-post.js b/models/forms/make-post.js index 3619e6be..f1fee629 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -1,11 +1,10 @@ 'use strict'; -const uuidv4 = require('uuid/v4') - , path = require('path') +const path = require('path') , util = require('util') , crypto = require('crypto') , randomBytes = util.promisify(crypto.randomBytes) - , remove = require('fs-extra').remove + , { remove, pathExists } = require('fs-extra') , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , Posts = require(__dirname+'/../../db/posts.js') , getTripCode = require(__dirname+'/../../helpers/tripcode.js') @@ -88,40 +87,43 @@ module.exports = async (req, res, next, numFiles) => { // then upload, thumb, get metadata, etc. for (let i = 0; i < numFiles; i++) { const file = req.files.file[i]; - const uuid = uuidv4(); - const filename = uuid + path.extname(file.name); + const filename = file.sha256 + path.extname(file.name); file.filename = filename; //for error to delete failed files //get metadata let processedFile = { + hash: file.sha256, filename: filename, originalFilename: file.name, mimetype: file.mimetype, size: file.size, }; + //check if already exists + const existsFull = await pathExists(`${uploadDirectory}img/${filename}`); + const existsThumb = await pathExists(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.jpg`); + //handle video/image ffmpeg or graphicsmagick const mainType = file.mimetype.split('/')[0]; switch (mainType) { case 'image': - await imageUpload(file, filename, 'img'); - const imageData = await imageIdentify(filename, 'img'); + const imageData = await imageIdentify(req.files.file[i].tempFilePath, null, true); processedFile.geometry = imageData.size // object with width and height pixels processedFile.sizeString = formatSize(processedFile.size) // 123 Ki string processedFile.geometryString = imageData.Geometry // 123 x 123 string - if (fileCheckMimeType(file.mimetype, {image: true}) //always thumbnail gif/webp + processedFile.hasThumb = !(fileCheckMimeType(file.mimetype, {image: true}) && processedFile.geometry.height <= 128 - && processedFile.geometry.width <= 128) { - processedFile.hasThumb = false; - } else { - processedFile.hasThumb = true; + && processedFile.geometry.width <= 128); + if (!existsFull) { + await imageUpload(file, filename, 'img'); + } + if (!existsThumb && processedFile.hasThumb) { await imageThumbnail(filename); } break; case 'video': //video metadata - await videoUpload(file, filename, 'img'); - const videoData = await videoIdentify(filename); + const videoData = await videoIdentify(req.files.file[i].tempFilePath, null, true); videoData.streams = videoData.streams.filter(stream => stream.width != null); //filter to only video streams or something with a resolution processedFile.duration = videoData.format.duration; processedFile.durationString = new Date(videoData.format.duration*1000).toLocaleString('en-US', {hour12:false}).split(' ')[1].replace(/^00:/, ''); @@ -129,10 +131,15 @@ module.exports = async (req, res, next, numFiles) => { processedFile.sizeString = formatSize(processedFile.size) // 123 Ki string processedFile.geometryString = `${processedFile.geometry.width}x${processedFile.geometry.height}` // 123 x 123 string processedFile.hasThumb = true; - await videoThumbnail(filename); + if (!existsFull) { + await videoUpload(file, filename, 'img'); + } + if (!existsThumb) { + await videoThumbnail(filename); + } break; default: - return next(new Error(`invalid file mime type: ${mainType}`)); + throw new Error(`invalid file mime type: ${mainType}`); //throw so goes to error handler before next'ing } //delete the temp file @@ -197,9 +204,12 @@ module.exports = async (req, res, next, numFiles) => { //simple markdown and sanitize let message = req.body.message; + let quotes = []; if (message && message.length > 0) { message = simpleMarkdown(req.params.board, req.body.thread, message); - message = await linkQuotes(req.params.board, message); + const { quotedMessage, threadQuotes } = await linkQuotes(req.params.board, message); + message = quotedMessage; + quotes = threadQuotes; message = sanitize(message, sanitizeOptions); } @@ -223,6 +233,7 @@ module.exports = async (req, res, next, numFiles) => { files, 'reports': [], 'globalreports': [], + quotes } if (!req.body.thread) { @@ -248,7 +259,7 @@ module.exports = async (req, res, next, numFiles) => { if (data.thread) { //refersh pages const threadPage = await Posts.getThreadPage(req.params.board, thread); - if (data.email === 'sage') { + if (data.email === 'sage' || thread.sage) { //refresh the page that the thread is on parallelPromises.push(buildBoard(res.locals.board, threadPage)); } else { @@ -259,6 +270,14 @@ module.exports = async (req, res, next, numFiles) => { //new thread, rebuild all pages and prunes old threads const prunedThreads = await Posts.pruneOldThreads(req.params.board, res.locals.board.settings.threadLimit); for (let i = 0; i < prunedThreads.length; i++) { + //TODO: consider: + //should i keep these? as an "archive" since they are removed from the DB + //posting wouldnt work and it would just be served as a static file + //files dont matter + //or i could add and set to "archive:true" with the same affect as locking + //but additionally does not appear in board index/catalog but allows to be rebuilt for template updates? + //or is a first party archive kinda against the point of an imageboard? + //i feel like threads epiring and not existing anymore is part of the design parallelPromises.push(remove(`${uploadDirectory}html/${req.params.board}/thread/${prunedThreads[i]}.html`)); } parallelPromises.push(buildBoardMultiple(res.locals.board, 1, 10)); diff --git a/package-lock.json b/package-lock.json index ab716328..2e219078 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,7 +396,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "dev": true, + "optional": true }, "assign-symbols": { "version": "1.0.0", @@ -582,6 +583,14 @@ } } }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, "bcrypt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.6.tgz", @@ -961,6 +970,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "dev": true, + "optional": true, "requires": { "delayed-stream": "~1.0.0" } @@ -1355,7 +1365,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "dev": true, + "optional": true }, "delegates": { "version": "1.0.0", @@ -1857,7 +1868,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "dev": true, + "optional": true }, "fancy-log": { "version": "1.3.3", @@ -2136,7 +2148,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2157,12 +2170,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2177,17 +2192,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2304,7 +2322,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2316,6 +2335,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2330,6 +2350,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2337,12 +2358,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2361,6 +2384,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2441,7 +2465,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2453,6 +2478,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2538,7 +2564,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2574,6 +2601,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2593,6 +2621,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2636,12 +2665,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -3552,7 +3583,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "dev": true, + "optional": true }, "json-schema": { "version": "0.2.3", @@ -4070,6 +4102,18 @@ "require_optional": "~1.0.0" } }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5815,7 +5859,8 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "dev": true, + "optional": true }, "type-is": { "version": "1.6.16", diff --git a/package.json b/package.json index 67d04b20..723160b2 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "fs-extra": "^7.0.1", "helmet": "^3.16.0", "mongodb": "^3.2.3", + "morgan": "^1.9.1", "path": "^0.12.7", "pug": "^2.0.3", "sanitize-html": "^1.20.0", From f6e8565432e066d3c1a961c29fd3f2469c4da661 Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 12:33:50 +0000 Subject: [PATCH 15/26] style changes --- gulp/res/css/style.css | 4 ++-- views/includes/postform.pug | 2 +- views/mixins/post.pug | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css index b4c2175a..95932319 100644 --- a/gulp/res/css/style.css +++ b/gulp/res/css/style.css @@ -117,8 +117,8 @@ object { .catalog-thumb { box-shadow: 0 0 3px black; - min-width: 64px; - min-height: 64px; + width: 64px; + height: 64px; object-fit: cover; } diff --git a/views/includes/postform.pug b/views/includes/postform.pug index 33d76092..496f2f28 100644 --- a/views/includes/postform.pug +++ b/views/includes/postform.pug @@ -30,7 +30,7 @@ section.form-wrapper.flex-center if board.settings.maxFiles !== 0 section.postform-row .postform-label Files - input#file(type='file', name='file' multiple='multiple') + input#file(type='file', name='file' multiple) label.postform-style.ph-5.ml-1 input#spoiler(type='checkbox', name='spoiler', value='true') | Spoiler diff --git a/views/mixins/post.pug b/views/mixins/post.pug index 4a6fb891..f4ab7a13 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -37,7 +37,7 @@ mixin post(post, truncate, manage=false, globalmanage=false) if post.userId span.user-id(style=`background: #${post.userId}`) #{post.userId} | - span No.#[a(href=postURL) #{post.postId}] + span: a(href=postURL) No.#{post.postId} if !post.thread | span: a(href=`/${post.board}/thread/${post.thread || post.postId}.html#postform`) [Reply] From f8a88d7a239a61ef59660295234782b52725a06a Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 12:34:42 +0000 Subject: [PATCH 16/26] prevent dup[licate backlinks and add (OP) label to op quotes --- helpers/quotes.js | 4 ++-- models/forms/make-post.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers/quotes.js b/helpers/quotes.js index 92a1928b..fcd1fac9 100644 --- a/helpers/quotes.js +++ b/helpers/quotes.js @@ -77,7 +77,7 @@ module.exports = async (board, text) => { const quotenum = +match.substring(2); if (postThreadIdMap[board] && postThreadIdMap[board][quotenum]) { threadQuotes.push(quotenum) - return `>>${quotenum}`; + return `>>${quotenum}${postThreadIdMap[board][quotenum] === quotenum ? ' (OP) ' : ''}`; } return match; }); @@ -96,6 +96,6 @@ module.exports = async (board, text) => { }); } - return { quotedMessage: text, threadQuotes }; + return { quotedMessage: text, threadQuotes: [...new Set(threadQuotes)] }; } diff --git a/models/forms/make-post.js b/models/forms/make-post.js index f1fee629..977950db 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -12,7 +12,7 @@ const path = require('path') , simpleMarkdown = require(__dirname+'/../../helpers/markdown.js') , sanitize = require('sanitize-html') , sanitizeOptions = { - allowedTags: [ 'span', 'a', 'em', 'strong' ], + allowedTags: [ 'span', 'a', 'em', 'strong', 'small' ], allowedAttributes: { 'a': [ 'href', 'class' ], 'span': [ 'class' ] From c38532aeae4be16da44bd52fb702dfcccf8aa418 Mon Sep 17 00:00:00 2001 From: fatchan Date: Tue, 28 May 2019 12:37:37 +0000 Subject: [PATCH 17/26] hash check for existing banners, fix gif banners, and ensure banner dir for board exists (gm cant create the path) --- models/forms/uploadbanners.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/models/forms/uploadbanners.js b/models/forms/uploadbanners.js index 5dd8fb66..2c26f85b 100644 --- a/models/forms/uploadbanners.js +++ b/models/forms/uploadbanners.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path') - , remove = require('fs-extra').remove + , { remove, pathExists, ensureDir } = require('fs-extra') , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , imageUpload = require(__dirname+'/../../helpers/files/imageupload.js') , fileCheckMimeType = require(__dirname+'/../../helpers/files/file-check-mime-types.js') @@ -32,23 +32,40 @@ module.exports = async (req, res, next, numFiles) => { file.filename = filename; //for error to delete failed files filenames.push(filename); -//todo: CHECK FILE HASHES before uploading, if exists skip identifying and uploading - //upload and get metadata + //check if already exists + const exists = await pathExists(`${uploadDirectory}banner/${req.params.board}/${filename}`); + if (exists) { + await deleteTempFiles(req.files.file); + return res.status(409).render('message', { + 'title': 'Conflict', + 'message': `Invalid file ${file.name}. Banner already exists.`, + 'redirect': redirect + }); + } + + await ensureDir(`${uploadDirectory}banner/${req.params.board}/`); + + //get metadata from tempfile const imageData = await imageIdentify(req.files.file[i].tempFilePath, null, true); - const geometry = imageData.size; + let geometry = imageData.size; + if (Array.isArray(geometry)) { + geometry = geometry[0]; + } //make sure its 300x100 banner if (geometry.width !== 300 || geometry.height !== 100) { - await deleteTempFiles(req.files.file) + await deleteTempFiles(req.files.file); return res.status(400).render('message', { 'title': 'Bad request', 'message': `Invalid file ${file.name}. Banners must be 300x100.`, 'redirect': redirect }); } + + //then upload it await imageUpload(file, filename, `banner/${req.params.board}`); -//end todo + //and delete the temp file await remove(file.tempFilePath); } From 491d0de81ece0ad8a2a886827cfe20520ebebf7a Mon Sep 17 00:00:00 2001 From: fatchan Date: Wed, 29 May 2019 03:01:06 +0000 Subject: [PATCH 18/26] make sure thumbnails for videos never exceed 128px in either dimension, while also maintaining aspect radio --- gulp/res/css/style.css | 3 ++- helpers/files/video-thumbnail.js | 5 +++-- models/forms/make-post.js | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css index 95932319..39d6258f 100644 --- a/gulp/res/css/style.css +++ b/gulp/res/css/style.css @@ -327,7 +327,8 @@ td, th { } .post-file-src { - margin: 0 auto; + justify-content: center; + display: flex; } .file-thumb { diff --git a/helpers/files/video-thumbnail.js b/helpers/files/video-thumbnail.js index 28d12f84..c05e7fc9 100644 --- a/helpers/files/video-thumbnail.js +++ b/helpers/files/video-thumbnail.js @@ -2,7 +2,7 @@ const ffmpeg = require('fluent-ffmpeg') , configs = require(__dirname+'/../../configs/main.json') , uploadDirectory = require(__dirname+'/../uploadDirectory.js'); -module.exports = (filename) => { +module.exports = (filename, geometry) => { return new Promise((resolve, reject) => { ffmpeg(`${uploadDirectory}img/${filename}`) @@ -14,7 +14,8 @@ module.exports = (filename) => { count: 1, filename: `thumb-${filename.split('.')[0]}.jpg`, folder: `${uploadDirectory}img/`, - size: '128x?' + size: geometry.width > geometry.height ? '128x?' : '?x128' + //keep aspect ratio, but also making sure taller/wider thumbs dont exceed 128 in either dimension }); }); diff --git a/models/forms/make-post.js b/models/forms/make-post.js index 977950db..f86ad885 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -135,7 +135,7 @@ module.exports = async (req, res, next, numFiles) => { await videoUpload(file, filename, 'img'); } if (!existsThumb) { - await videoThumbnail(filename); + await videoThumbnail(filename, processedFile.geometry); } break; default: From 756d15cfd31dea0499e4524161e05f5319d8b098 Mon Sep 17 00:00:00 2001 From: fatchan Date: Wed, 29 May 2019 15:36:18 +0000 Subject: [PATCH 19/26] update deps --- package-lock.json | 440 ++++++++++++++++++++++++++++------------------ package.json | 16 +- 2 files changed, 278 insertions(+), 178 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e219078..7e7fcfe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,8 +5,8 @@ "requires": true, "dependencies": { "@tohru/gm": { - "version": "git+https://github.com/iCrawl/gm.git#70ade5ebee96db0e38d621eb8f9744e5eee159c7", - "from": "git+https://github.com/iCrawl/gm.git", + "version": "git+https://github.com/fatchan/gm.git#5f0ec1a0a262be8e4ac3916886ee55576db2c026", + "from": "git+https://github.com/fatchan/gm.git", "requires": { "array-parallel": "^0.1.3", "array-series": "^0.1.5", @@ -43,18 +43,62 @@ "@types/babel-types": "*" } }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.3.tgz", + "integrity": "sha512-zkOxCS/fA+3SsdA+9Yun0iANxzhQRiNwTvJSr6N95JhuJ/x27z9G2URx1Jpt3zYFfCGUXZGL5UDxt5eyLE7wgw==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, "accord": { @@ -617,20 +661,35 @@ "dev": true }, "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } } }, "brace-expansion": { @@ -726,9 +785,9 @@ } }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "cache-base": { "version": "1.0.1", @@ -792,9 +851,9 @@ } }, "chokidar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", - "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -1103,9 +1162,12 @@ } }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } }, "content-security-policy-builder": { "version": "2.0.0", @@ -1348,11 +1410,12 @@ } }, "del": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.0.tgz", - "integrity": "sha512-C4kvKNlYrwXhKxz97BuohF8YoGgQ23Xm9lvoHmgT7JaPGprSEjk3+XFled74Yt/x0ZABUHg2D67covzAPUKx5Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", "dev": true, "requires": { + "@types/glob": "^7.1.1", "globby": "^6.1.0", "is-path-cwd": "^2.0.0", "is-path-in-cwd": "^2.0.0", @@ -1575,9 +1638,9 @@ } }, "es5-ext": { - "version": "0.10.49", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz", - "integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==", + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", "dev": true, "requires": { "es6-iterator": "~2.0.3", @@ -1688,51 +1751,56 @@ } }, "expect-ct": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.1.1.tgz", - "integrity": "sha512-ngXzTfoRGG7fYens3/RMb6yYoVLvLMfmsSllP/mZPxNHgFq41TmPSLF/nLY7fwoclI2vElvAmILFWGUYqdjfCg==" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", + "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" }, "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" } } }, @@ -1898,9 +1966,9 @@ "optional": true }, "feature-policy": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.2.0.tgz", - "integrity": "sha512-2hGrlv6efG4hscYVZeaYjpzpT6I2OZgYqE2yDUzeAcKj2D1SH0AsEzqJNXzdoglEddcIXQQYop3lD97XpG75Jw==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" }, "fill-range": { "version": "4.0.0", @@ -1926,24 +1994,17 @@ } }, "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } } }, "find-up": { @@ -2081,9 +2142,9 @@ } }, "frameguard": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.0.0.tgz", - "integrity": "sha1-e8rUae57lukdEs6zlZx4I1qScuk=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", + "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" }, "fresh": { "version": "0.5.2", @@ -2129,9 +2190,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", - "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, "optional": true, "requires": { @@ -2870,9 +2931,9 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "gulp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.1.tgz", - "integrity": "sha512-yDVtVunxrAdsk7rIV/b7lVSBifPN1Eqe6wTjsESGrFcL+MEVzaaeNTkpUuGTUptloSOU+8oJm/lBJbgPV+tMAw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, "requires": { "glob-watcher": "^5.0.3", @@ -3145,24 +3206,24 @@ } }, "helmet": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.16.0.tgz", - "integrity": "sha512-rsTKRogc5OYGlvSHuq5QsmOsOzF6uDoMqpfh+Np8r23+QxDq+SUx90Rf8HyIKQVl7H6NswZEwfcykinbAeZ6UQ==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.18.0.tgz", + "integrity": "sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA==", "requires": { "depd": "2.0.0", "dns-prefetch-control": "0.1.0", "dont-sniff-mimetype": "1.0.0", - "expect-ct": "0.1.1", - "feature-policy": "0.2.0", - "frameguard": "3.0.0", + "expect-ct": "0.2.0", + "feature-policy": "0.3.0", + "frameguard": "3.1.0", "helmet-crossdomain": "0.3.0", "helmet-csp": "2.7.1", "hide-powered-by": "1.0.0", "hpkp": "2.0.0", "hsts": "2.2.0", "ienoopen": "1.1.0", - "nocache": "2.0.0", - "referrer-policy": "1.1.0", + "nocache": "2.1.0", + "referrer-policy": "1.2.0", "x-xss-protection": "1.1.0" }, "dependencies": { @@ -3243,14 +3304,15 @@ } }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, "http-signature": { @@ -3463,27 +3525,27 @@ } }, "is-path-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.0.0.tgz", - "integrity": "sha512-m5dHHzpOXEiv18JEORttBO64UgTEypx99vCxQLjbBvGhOJxnTNglYoFXxwo6AbsQb79sqqycQEHv2hWkHZAijA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.1.0.tgz", + "integrity": "sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==", "dev": true }, "is-path-in-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.0.0.tgz", - "integrity": "sha512-6Vz5Gc9s/sDA3JBVu0FzWufm8xaBsqy1zn8Q6gmvGP6nSDMw78aS4poBNeatWjaRpTpxxLn1WOndAiOlk+qY8A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "is-path-inside": "^2.1.0" } }, "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "^1.0.2" } }, "is-plain-object": { @@ -3985,17 +4047,23 @@ "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, + "optional": true }, "mime-db": { "version": "1.35.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "dev": true, + "optional": true }, "mime-types": { "version": "2.1.19", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "dev": true, + "optional": true, "requires": { "mime-db": "~1.35.0" } @@ -4067,11 +4135,11 @@ } }, "mongodb": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.3.tgz", - "integrity": "sha512-jw8UyPsq4QleZ9z+t/pIVy3L++51vKdaJ2Q/XXeYxk/3cnKioAH8H6f5tkkDivrQL4PUgUOHe9uZzkpRFH1XtQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.6.tgz", + "integrity": "sha512-qnHc4tjEkHKemuzBq9R7ycYnhFE0Dlpt6+n6suoZp2DcDdqviQ+teloJU24fsOw/PLmr75yGk4mRx/YabjDQEQ==", "requires": { - "mongodb-core": "^3.2.3", + "mongodb-core": "3.2.6", "safe-buffer": "^5.1.2" }, "dependencies": { @@ -4081,9 +4149,9 @@ "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==" }, "mongodb-core": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.3.tgz", - "integrity": "sha512-UyI0rmvPPkjOJV8XGWa9VCTq7R4hBVipimhnAXeSXnuAPjuTqbyfA5Ec9RcYJ1Hhu+ISnc8bJ1KfGZd4ZkYARQ==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.6.tgz", + "integrity": "sha512-i+XRVjur9D0ywGF7cFebOUnALnbvMHajdNhhl3TQuopW6QDE655G8CpPeERbqSqfa3rOKEUo08lENDIiBIuAvQ==", "requires": { "bson": "^1.1.1", "require_optional": "^1.0.1", @@ -4183,9 +4251,9 @@ } }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "next-tick": { "version": "1.0.0", @@ -4194,9 +4262,9 @@ "dev": true }, "nocache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz", - "integrity": "sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" }, "node-pre-gyp": { "version": "0.12.0", @@ -4237,9 +4305,9 @@ }, "dependencies": { "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -4667,9 +4735,9 @@ "dev": true }, "postcss": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz", - "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==", + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.16.tgz", + "integrity": "sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA==", "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -4883,7 +4951,9 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "optional": true }, "random-bytes": { "version": "1.0.0", @@ -4891,19 +4961,29 @@ "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "rc": { @@ -5002,9 +5082,9 @@ } }, "referrer-policy": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz", - "integrity": "sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" }, "regenerator-runtime": { "version": "0.11.1", @@ -5211,9 +5291,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-html": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.0.tgz", - "integrity": "sha512-BpxXkBoAG+uKCHjoXFmox6kCSYpnulABoGcZ/R3QyY9ndXbIM5S94eOr1IqnzTG8TnbmXaxWoDDzKC5eJv7fEQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz", + "integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==", "requires": { "chalk": "^2.4.1", "htmlparser2": "^3.10.0", @@ -5228,9 +5308,9 @@ } }, "saslprep": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz", - "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", "optional": true, "requires": { "sparse-bitfield": "^3.0.3" @@ -5256,9 +5336,9 @@ } }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -5267,30 +5347,35 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { @@ -5322,9 +5407,9 @@ } }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "signal-exit": { "version": "3.0.2", @@ -5863,12 +5948,27 @@ "optional": true }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "~2.1.24" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + } } }, "typedarray": { @@ -6084,9 +6184,9 @@ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "v8flags": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.2.tgz", - "integrity": "sha512-MtivA7GF24yMPte9Rp/BWGCYQNaUj86zeYxV/x2RRJMKagImbbv3u8iJC57lNhWLPcGLJmHcHmFWkNsplbbLWw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", "dev": true, "requires": { "homedir-polyfill": "^1.0.1" diff --git a/package.json b/package.json index 723160b2..2ffd0f0d 100644 --- a/package.json +++ b/package.json @@ -4,29 +4,29 @@ "description": "", "main": "server.js", "dependencies": { - "@tohru/gm": "git+https://github.com/iCrawl/gm.git", + "@tohru/gm": "git+https://github.com/fatchan/gm.git", "bcrypt": "^3.0.6", - "body-parser": "^1.18.3", + "body-parser": "^1.19.0", "connect-mongo": "^2.0.3", "cookie-parser": "^1.4.4", "csurf": "^1.10.0", - "express": "^4.16.4", + "express": "^4.17.1", "express-fileupload": "^1.1.4", "express-session": "^1.16.1", "fluent-ffmpeg": "^2.1.2", "fs": "0.0.1-security", "fs-extra": "^7.0.1", - "helmet": "^3.16.0", - "mongodb": "^3.2.3", + "helmet": "^3.18.0", + "mongodb": "^3.2.6", "morgan": "^1.9.1", "path": "^0.12.7", "pug": "^2.0.3", - "sanitize-html": "^1.20.0", + "sanitize-html": "^1.20.1", "uuid": "^3.3.2" }, "devDependencies": { - "del": "^4.1.0", - "gulp": "^4.0.1", + "del": "^4.1.1", + "gulp": "^4.0.2", "gulp-clean-css": "^4.2.0", "gulp-concat": "^2.6.1", "gulp-less": "^4.0.1", From 09412d5e8591844f60cc8aa2e4732a72736b0692 Mon Sep 17 00:00:00 2001 From: fatchan Date: Wed, 29 May 2019 15:38:04 +0000 Subject: [PATCH 20/26] aggregate query with unwind/sample for random banner selection, and delete banners fixed for board specific path --- models/forms/deletebanners.js | 2 +- models/pages/banners.js | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/models/forms/deletebanners.js b/models/forms/deletebanners.js index 84224289..2d4ba234 100644 --- a/models/forms/deletebanners.js +++ b/models/forms/deletebanners.js @@ -9,7 +9,7 @@ module.exports = async (req, res, next) => { const redirect = `/${req.params.board}/manage.html` await Promise.all(req.body.checkedbanners.map(async filename => { - remove(`${uploadDirectory}banner/${filename}`); + remove(`${uploadDirectory}banner/${req.params.board}/${filename}`); })); await Boards.removeBanners(req.params.board, req.body.checkedbanners); diff --git a/models/pages/banners.js b/models/pages/banners.js index c3ff2e5f..33c36290 100644 --- a/models/pages/banners.js +++ b/models/pages/banners.js @@ -8,21 +8,20 @@ module.exports = async (req, res, next) => { return next(); } - // get all threads - let board; - try { - board = await Boards.findOne(req.query.board); - } catch (err) { - return next(err); - } - - if (!board) { - return next(); - } - - if (board.banners.length > 0) { - const randomBanner = board.banners[Math.floor(Math.random()*board.banners.length)]; - return res.redirect(`/banner/${req.query.board}/${randomBanner}`); + //agregate to get single random item from banners array + const board = await Boards.db.aggregate([ + { + '$unwind': '$banners' + }, + { + '$sample': { + 'size' : 1 + } + } + ]).toArray().then(res => res[0]); + + if (board && board.banners != null) { + return res.redirect(`/banner/${req.query.board}/${board.banners}`); } return res.redirect('/img/defaultbanner.png'); From 36a3755dca38bff7fea2c72ce1da2e2441b2981b Mon Sep 17 00:00:00 2001 From: fatchan Date: Fri, 31 May 2019 02:45:03 +0000 Subject: [PATCH 21/26] file upload mmiddleware only on valid routes, 10 banners at a time upload, and show how many _new_ banners uploaded --- controllers/forms.js | 30 ++++++++++++++++++++++++++++-- models/forms/uploadbanners.js | 15 +++++++++++---- server.js | 15 +-------------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/controllers/forms.js b/controllers/forms.js index 51ea54fb..61032509 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -6,6 +6,32 @@ const express = require('express') , Posts = require(__dirname+'/../db/posts.js') , Mongo = require(__dirname+'/../db/db.js') , remove = require('fs-extra').remove + , upload = require('express-fileupload') + , path = require('path') + , postFiles = upload({ + createParentPath: true, + safeFileNames: /[^\w-]+/g, + preserveExtension: 4, + limits: { + fileSize: 10 * 1024 * 1024, + files: 3 + }, + abortOnLimit: true, + useTempFiles: true, + tempFileDir: path.join(__dirname+'/../tmp/') + }) + , bannerFiles = upload({ + createParentPath: true, + safeFileNames: /[^\w-]+/g, + preserveExtension: 4, + limits: { + fileSize: 10 * 1024 * 1024, + files: 10 + }, + abortOnLimit: true, + useTempFiles: true, + tempFileDir: path.join(__dirname+'/../tmp/') + }) , removeBans = require(__dirname+'/../models/forms/removebans.js') , makePost = require(__dirname+'/../models/forms/make-post.js') , uploadBanners = require(__dirname+'/../models/forms/uploadbanners.js') @@ -152,7 +178,7 @@ router.post('/register', verifyCaptcha, (req, res, next) => { }); // make new post -router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verifyCaptcha, async (req, res, next) => { +router.post('/board/:board/post', Boards.exists, banCheck, postFiles, paramConverter, verifyCaptcha, async (req, res, next) => { let numFiles = 0; if (req.files && req.files.file) { @@ -255,7 +281,7 @@ router.post('/board/:board/settings', csrf, Boards.exists, checkPermsMiddleware, }); //upload banners -router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => { +router.post('/board/:board/addbanners', bannerFiles, csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => { let numFiles = 0; if (req.files && req.files.file) { diff --git a/models/forms/uploadbanners.js b/models/forms/uploadbanners.js index 2c26f85b..88291d3e 100644 --- a/models/forms/uploadbanners.js +++ b/models/forms/uploadbanners.js @@ -29,20 +29,27 @@ module.exports = async (req, res, next, numFiles) => { for (let i = 0; i < numFiles; i++) { const file = req.files.file[i]; const filename = file.sha256 + path.extname(file.name); - file.filename = filename; //for error to delete failed files - filenames.push(filename); + file.filename = filename; //check if already exists const exists = await pathExists(`${uploadDirectory}banner/${req.params.board}/${filename}`); + if (exists) { - await deleteTempFiles(req.files.file); + await remove(file.tempFilePath); + continue; +/* dont stop uploading the other banners just because one already exists. return res.status(409).render('message', { 'title': 'Conflict', 'message': `Invalid file ${file.name}. Banner already exists.`, 'redirect': redirect }); +*/ } + //add to list after checking it doesnt already exist + filenames.push(filename); + + //make directory if doesnt exist await ensureDir(`${uploadDirectory}banner/${req.params.board}/`); //get metadata from tempfile @@ -76,7 +83,7 @@ module.exports = async (req, res, next, numFiles) => { return res.render('message', { 'title': 'Success', - 'message': `Uploaded ${filenames.length} banners.`, + 'message': `Uploaded ${filenames.length} new banners.`, 'redirect': redirect }); diff --git a/server.js b/server.js index 602dbe2d..1f35c7d9 100644 --- a/server.js +++ b/server.js @@ -12,8 +12,7 @@ const express = require('express') , bodyParser = require('body-parser') , cookieParser = require('cookie-parser') , configs = require(__dirname+'/configs/main.json') - , Mongo = require(__dirname+'/db/db.js') - , upload = require('express-fileupload'); + , Mongo = require(__dirname+'/db/db.js'); (async () => { @@ -23,18 +22,6 @@ const express = require('express') // parse forms and allow file uploads app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); - app.use(upload({ - createParentPath: true, - safeFileNames: true, - preserveExtension: 4, - limits: { - fileSize: 10 * 1024 * 1024, - files: 3 - }, - abortOnLimit: true, - useTempFiles: true, - tempFileDir: path.join(__dirname+'/tmp/') - })); // session store app.set('trust proxy', 1); From 5e1e0f7ef933fad71bc132e10ac2a55fc9b58128 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sat, 1 Jun 2019 11:20:59 +0000 Subject: [PATCH 22/26] backlinking refactor, remove helmet from server since its basically useless --- build.js | 115 ++++++++++++++++++------------------------------------ server.js | 27 +++++++------ 2 files changed, 51 insertions(+), 91 deletions(-) diff --git a/build.js b/build.js index eb27cd53..baef993b 100644 --- a/build.js +++ b/build.js @@ -5,6 +5,36 @@ const Posts = require(__dirname+'/db/posts.js') , uploadDirectory = require(__dirname+'/helpers/uploadDirectory.js') , render = require(__dirname+'/helpers/render.js'); + +function addBacklinks(thread, preview) { //preview means this is not the full thread + const postMap = new Map() + postMap.set(thread.postId, thread) + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + postMap.set(reply.postId, reply); + } + for (let i = 0; i < thread.replies.length; i++) { + const reply = thread.replies[i]; + if (!reply.quotes) continue; + for (let j = 0; j < reply.quotes.length; j++) { + const quote = reply.quotes[j]; + if (postMap.has(quote)) { + const post = postMap.get(quote) + if (!post.backlinks) { + post.backlinks = []; + } + post.backlinks.push(reply.postId); + } else if (!preview) { + /* + quote was valid on post creation, but points to postID that has been deleted + or possibly removed from cyclical thread (whenever i implement those) + could re-markdown the post here to remove the quotes (or convert to greentext) + */ + } + } + } +} + module.exports = { buildCatalog: async (board) => { @@ -29,32 +59,7 @@ module.exports = { return; //this thread may have been an OP that was deleted } - /* - temporary, jsut seeing how well this works - */ - const postMap = new Map() - postMap.set(thread.postId, thread) - for (let i = 0; i < thread.replies.length; i++) { - const reply = thread.replies[i]; - postMap.set(reply.postId, reply); - } - for (let i = 0; i < thread.replies.length; i++) { - const reply = thread.replies[i]; - if (!reply.quotes) continue; - for (let j = 0; j < reply.quotes.length; j++) { - const quote = reply.quotes[j]; - if (postMap.has(quote)) { - const post = postMap.get(quote) - if (!post.backlinks) { - post.backlinks = []; - } - post.backlinks.push(reply.postId); - } - } - } - /* - temporary, jsut seeing how well this works - */ + addBacklinks(thread, false); return render(`${board._id}/thread/${threadId}.html`, 'thread.pug', { board, @@ -68,35 +73,12 @@ module.exports = { if (!maxPage) { maxPage = Math.ceil((await Posts.getPages(board._id)) / 10); } - /* - temporary, jsut seeing how well this works - */ + for (let k = 0; k < threads.length; k++) { const thread = threads[k]; - const postMap = new Map() - postMap.set(thread.postId, thread) - for (let i = 0; i < thread.replies.length; i++) { - const reply = thread.replies[i]; - postMap.set(reply.postId, reply); - } - for (let i = 0; i < thread.replies.length; i++) { - const reply = thread.replies[i]; - if (!reply.quotes) continue; - for (let j = 0; j < reply.quotes.length; j++) { - const quote = reply.quotes[j]; - if (postMap.has(quote)) { - const post = postMap.get(quote) - if (!post.backlinks) { - post.backlinks = []; - } - post.backlinks.push(reply.postId); - } - } - } + addBacklinks(thread, true); } - /* - temporary, jsut seeing how well this works - */ + return render(`${board._id}/${page === 1 ? 'index' : page}.html`, 'board.pug', { board, threads, @@ -117,35 +99,12 @@ module.exports = { } const difference = endpage-startpage + 1; //+1 because for single pagemust be > 0 const threads = await Posts.getRecent(board._id, startpage, difference*10); - /* - temporary, jsut seeing how well this works - */ + for (let k = 0; k < threads.length; k++) { const thread = threads[k]; - const postMap = new Map() - postMap.set(thread.postId, thread) - for (let i = 0; i < thread.replies.length; i++) { - const reply = thread.replies[i]; - postMap.set(reply.postId, reply); - } - for (let i = 0; i < thread.replies.length; i++) { - const reply = thread.replies[i]; - if (!reply.quotes) continue; - for (let j = 0; j < reply.quotes.length; j++) { - const quote = reply.quotes[j]; - if (postMap.has(quote)) { - const post = postMap.get(quote) - if (!post.backlinks) { - post.backlinks = []; - } - post.backlinks.push(reply.postId); - } - } - } + addBacklinks(thread, true); } - /* - temporary, jsut seeing how well this works - */ + const buildArray = []; for (let i = startpage; i <= endpage; i++) { //console.log('multi building board page', `${board._id}/${i === 1 ? 'index' : i}.html`); diff --git a/server.js b/server.js index 1f35c7d9..f0e20568 100644 --- a/server.js +++ b/server.js @@ -3,12 +3,11 @@ process.on('uncaughtException', console.error); process.on('unhandledRejection', console.error); -const express = require('express') - , session = require('express-session') +const express = require('express') + , session = require('express-session') , MongoStore = require('connect-mongo')(session) - , path = require('path') - , app = express() - , helmet = require('helmet') + , path = require('path') + , app = express() , bodyParser = require('body-parser') , cookieParser = require('cookie-parser') , configs = require(__dirname+'/configs/main.json') @@ -19,12 +18,14 @@ const express = require('express') // let db connect await Mongo.connect(); - // parse forms and allow file uploads + // parse forms app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); + //parse cookies + app.use(cookieParser()); + // session store - app.set('trust proxy', 1); app.use(session({ secret: configs.sessionSecret, store: new MongoStore({ db: Mongo.client.db('sessions') }), @@ -36,10 +37,9 @@ const express = require('express') sameSite: 'lax', } })); - app.use(cookieParser()); - // csurf and helmet - app.use(helmet()); + //trust proxy for nginx + app.set('trust proxy', 1); //referer header check app.use((req, res, next) => { @@ -84,11 +84,11 @@ const express = require('express') // listen const server = app.listen(configs.port, '127.0.0.1', () => { - console.log(`Listening on port ${configs.port}`); + console.log(`listening on port ${configs.port}`); //let PM2 know that this is ready (for graceful reloads) if (typeof process.send === 'function') { //make sure we are a child process - console.info('Sending ready signal to PM2') + console.info('sending ready signal to PM2') process.send('ready'); } @@ -96,7 +96,7 @@ const express = require('express') process.on('SIGINT', () => { - console.info('SIGINT signal received.') + console.info('SIGINT signal received') // Stops the server from accepting new connections and finishes existing connections. server.close((err) => { @@ -109,6 +109,7 @@ const express = require('express') } // close database connection + console.info('closing db connection') Mongo.client.close(); // now close without error From 68b9596a76096a728313d99591b60562d8e8b9fc Mon Sep 17 00:00:00 2001 From: fatchan Date: Sat, 1 Jun 2019 11:53:40 +0000 Subject: [PATCH 23/26] show hashtag in capcodes and dont use board name as part of id hash --- models/forms/make-post.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/models/forms/make-post.js b/models/forms/make-post.js index f86ad885..9d158c96 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -169,7 +169,7 @@ module.exports = async (req, res, next, numFiles) => { salt = (await randomBytes(128)).toString('hex'); } if (res.locals.board.settings.ids) { - const fullUserIdHash = crypto.createHash('sha256').update(salt + ip + req.params.board).digest('hex'); + const fullUserIdHash = crypto.createHash('sha256').update(salt + ip).digest('hex'); userId = fullUserIdHash.substring(fullUserIdHash.length-6); } @@ -188,7 +188,7 @@ module.exports = async (req, res, next, numFiles) => { const groups = matches.groups; //name if (groups.name) { - name = groups.name + name = groups.name; } //tripcode if (groups.tripcode) { @@ -197,7 +197,7 @@ module.exports = async (req, res, next, numFiles) => { //capcode if (groups.capcode && hasPerms) { // TODO: add proper code for different capcodes - capcode = groups.capcode; + capcode = `## ${groups.capcode}`; } } } @@ -270,14 +270,6 @@ module.exports = async (req, res, next, numFiles) => { //new thread, rebuild all pages and prunes old threads const prunedThreads = await Posts.pruneOldThreads(req.params.board, res.locals.board.settings.threadLimit); for (let i = 0; i < prunedThreads.length; i++) { - //TODO: consider: - //should i keep these? as an "archive" since they are removed from the DB - //posting wouldnt work and it would just be served as a static file - //files dont matter - //or i could add and set to "archive:true" with the same affect as locking - //but additionally does not appear in board index/catalog but allows to be rebuilt for template updates? - //or is a first party archive kinda against the point of an imageboard? - //i feel like threads epiring and not existing anymore is part of the design parallelPromises.push(remove(`${uploadDirectory}html/${req.params.board}/thread/${prunedThreads[i]}.html`)); } parallelPromises.push(buildBoardMultiple(res.locals.board, 1, 10)); From 70618c950e21475c108b15d01751836a47b1a6e4 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sat, 1 Jun 2019 14:46:51 +0000 Subject: [PATCH 24/26] refactor param converter for number inputs --- helpers/paramconverter.js | 51 ++++++++------------------------------- 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js index 6ff9035b..65db8b96 100644 --- a/helpers/paramconverter.js +++ b/helpers/paramconverter.js @@ -2,6 +2,7 @@ const Mongo = require(__dirname+'/../db/db.js') , allowedArrays = new Set(['checkedposts', 'globalcheckedposts', 'checkedbans', 'checkedbanners']) + , numberFields = ['p'. 'reply_limit', 'max_files', 'thread_limit', 'id', 'thread'] module.exports = (req, res, next) => { @@ -26,47 +27,15 @@ module.exports = (req, res, next) => { req.body.globalcheckedposts = req.body.globalcheckedposts.map(Mongo.ObjectId) } - //thread in post form - if (req.params.id) { - req.params.id = +req.params.id; - } - if (req.body.thread) { - req.body.thread = +req.body.thread; - } - - //page number - if (req.query.p) { - const num = parseInt(req.query.p); - if (Number.isSafeInteger(num)) { - req.query.p = num; - } else { - req.query.p = null; - } - } - - //board settings - if (req.body.reply_limit != null) { - const num = parseInt(req.body.reply_limit); - if (Number.isSafeInteger(num)) { - req.body.reply_limit = num; - } else { - req.body.reply_limit = null; - } - } - if (req.body.max_files != null) { - const num = parseInt(req.body.max_files); - if (Number.isSafeInteger(num)) { - req.body.max_files = num; - } else { - req.body.max_files = null; - } - } - if (req.body.thread_limit != null) { - const num = +parseInt(req.body.thread_limit); - if (Number.isSafeInteger(num)) { - req.body.thread_limit = num; - } else { - req.body.thread_limit = null; + for (let i = 0; i < numberFields.length; i++) { + const field = numberFields[i]; + if (req.query[field]) { + const num = parseInt(req.query[field]); + if (Number.isSafeInteger(num)) { + req.query[field] = num; + } else { + req.query[field] = null; + } } } From 66d168863f11ed517c7f926d23bf2560cdbf5a5d Mon Sep 17 00:00:00 2001 From: fatchan Date: Sat, 1 Jun 2019 15:02:27 +0000 Subject: [PATCH 25/26] do that for body instead and remove old 'p' for page query not used anymore --- helpers/paramconverter.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js index 65db8b96..82068c0f 100644 --- a/helpers/paramconverter.js +++ b/helpers/paramconverter.js @@ -2,7 +2,7 @@ const Mongo = require(__dirname+'/../db/db.js') , allowedArrays = new Set(['checkedposts', 'globalcheckedposts', 'checkedbans', 'checkedbanners']) - , numberFields = ['p'. 'reply_limit', 'max_files', 'thread_limit', 'id', 'thread'] + , numberFields = ['reply_limit', 'max_files', 'thread_limit', 'thread'] module.exports = (req, res, next) => { @@ -26,15 +26,18 @@ module.exports = (req, res, next) => { if (req.body.globalcheckedposts) { req.body.globalcheckedposts = req.body.globalcheckedposts.map(Mongo.ObjectId) } + if (req.params.id) { + req.params.id = +req.params.id; + } for (let i = 0; i < numberFields.length; i++) { const field = numberFields[i]; - if (req.query[field]) { - const num = parseInt(req.query[field]); + if (req.body[field]) { + const num = parseInt(req.body[field]); if (Number.isSafeInteger(num)) { - req.query[field] = num; + req.body[field] = num; } else { - req.query[field] = null; + req.body[field] = null; } } } From c7d02a5a2b77e62a38501882b343b4fa6e6aa2d4 Mon Sep 17 00:00:00 2001 From: fatchan Date: Sun, 2 Jun 2019 16:30:39 +0000 Subject: [PATCH 26/26] temp files _actually_ deleted for uploads, still need a little refactor. also remove hyphen from filenames --- controllers/forms.js | 44 +++++++++---------- helpers/files/deletetempfiles.js | 20 +++++++-- .../files/{format-size.js => formatsize.js} | 0 .../{image-identify.js => imageidentify.js} | 0 .../{image-thumbnail.js => imagethumbnail.js} | 0 ...{file-check-mime-types.js => mimetypes.js} | 0 .../{video-identify.js => videoidentify.js} | 0 .../{video-thumbnail.js => videothumbnail.js} | 0 helpers/id-contrast.js | 8 ---- helpers/paramconverter.js | 2 +- models/forms/actionhandler.js | 10 ++--- models/forms/{ban-poster.js => banposter.js} | 0 .../forms/{delete-post.js => deletepost.js} | 0 .../{dismiss-report.js => dismissreport.js} | 0 models/forms/{make-post.js => makepost.js} | 18 +++++--- .../forms/{report-post.js => reportpost.js} | 0 .../forms/{spoiler-post.js => spoilerpost.js} | 0 models/forms/uploadbanners.js | 15 ++----- 18 files changed, 58 insertions(+), 59 deletions(-) rename helpers/files/{format-size.js => formatsize.js} (100%) rename helpers/files/{image-identify.js => imageidentify.js} (100%) rename helpers/files/{image-thumbnail.js => imagethumbnail.js} (100%) rename helpers/files/{file-check-mime-types.js => mimetypes.js} (100%) rename helpers/files/{video-identify.js => videoidentify.js} (100%) rename helpers/files/{video-thumbnail.js => videothumbnail.js} (100%) delete mode 100644 helpers/id-contrast.js rename models/forms/{ban-poster.js => banposter.js} (100%) rename models/forms/{delete-post.js => deletepost.js} (100%) rename models/forms/{dismiss-report.js => dismissreport.js} (100%) rename models/forms/{make-post.js => makepost.js} (94%) rename models/forms/{report-post.js => reportpost.js} (100%) rename models/forms/{spoiler-post.js => spoilerpost.js} (100%) diff --git a/controllers/forms.js b/controllers/forms.js index 61032509..4a663c01 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -33,13 +33,13 @@ const express = require('express') tempFileDir: path.join(__dirname+'/../tmp/') }) , removeBans = require(__dirname+'/../models/forms/removebans.js') - , makePost = require(__dirname+'/../models/forms/make-post.js') + , makePost = require(__dirname+'/../models/forms/makepost.js') + , deleteTempFiles = require(__dirname+'/../helpers/files/deletetempfiles.js') , uploadBanners = require(__dirname+'/../models/forms/uploadbanners.js') , deleteBanners = require(__dirname+'/../models/forms/deletebanners.js') , loginAccount = require(__dirname+'/../models/forms/login.js') , changePassword = require(__dirname+'/../models/forms/changepassword.js') , registerAccount = require(__dirname+'/../models/forms/register.js') - , deleteTempFiles = require(__dirname+'/../helpers/files/deletetempfiles.js') , checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js') , checkPerms = require(__dirname+'/../helpers/hasperms.js') , paramConverter = require(__dirname+'/../helpers/paramconverter.js') @@ -226,22 +226,18 @@ router.post('/board/:board/post', Boards.exists, banCheck, postFiles, paramConve } if (errors.length > 0) { - if (numFiles > 0) { - await deleteTempFiles(req.files.file) - } + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { - 'title': 'Bad request', - 'errors': errors, - 'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}` - }) + 'title': 'Bad request', + 'errors': errors, + 'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}` + }); } try { await makePost(req, res, next, numFiles); } catch (err) { - if (numFiles > 0) { - await deleteTempFiles(req.files.file) - } + await deleteTempFiles(req).catch(e => console.error); return next(err); } @@ -252,8 +248,8 @@ router.post('/board/:board/settings', csrf, Boards.exists, checkPermsMiddleware, const errors = []; - if (req.body.default_name && req.body.default_name.length > 20) { - errors.push('Must provide a message or file'); + if (req.body.default_name && req.body.default_name.length < 1 || req.body.default_name.length > 50) { + errors.push('Anon name must be 1-50 characters'); } if (typeof req.body.reply_limit === 'number' && (req.body.reply_limit < 1 || req.body.reply_limit > 1000)) { errors.push('Reply Limit must be from 1-1000'); @@ -273,10 +269,14 @@ router.post('/board/:board/settings', csrf, Boards.exists, checkPermsMiddleware, }) } - return res.status(501).render('message', { - 'title': 'Not implemented', - 'redirect': `/${req.params.board}/manage.html` - }) + try { + return res.status(501).render('message', { + 'title': 'Not implemented', + 'redirect': `/${req.params.board}/manage.html` + }) + } catch (err) { + return next(err); + } }); @@ -303,9 +303,7 @@ router.post('/board/:board/addbanners', bannerFiles, csrf, Boards.exists, checkP } if (errors.length > 0) { - if (numFiles > 0) { - await deleteTempFiles(req.files.file) - } + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'errors': errors, @@ -316,9 +314,7 @@ router.post('/board/:board/addbanners', bannerFiles, csrf, Boards.exists, checkP try { await uploadBanners(req, res, next, numFiles); } catch (err) { - if (numFiles > 0) { - await deleteTempFiles(req.files.file) - } + await deleteTempFiles(req).catch(e => console.error); return next(err); } diff --git a/helpers/files/deletetempfiles.js b/helpers/files/deletetempfiles.js index fe13a56b..6a383e10 100644 --- a/helpers/files/deletetempfiles.js +++ b/helpers/files/deletetempfiles.js @@ -2,10 +2,22 @@ const remove = require('fs-extra').remove; -module.exports = async (files) => { +module.exports = async (req) => { - return Promise.all(files.map(async file => { - remove(file.tempFilePath); - })); + if (req.files != null) { + let files = []; + const keys = Object.keys(req.files); + for (let i = 0; i < keys.length; i++) { + const val = req.files[keys[i]]; + if (Array.isArray(val)) { + files = files.concat(val); + } else { + files.push(val); + } + } + return Promise.all(files.map(async file => { + remove(file.tempFilePath); + })); + } } diff --git a/helpers/files/format-size.js b/helpers/files/formatsize.js similarity index 100% rename from helpers/files/format-size.js rename to helpers/files/formatsize.js diff --git a/helpers/files/image-identify.js b/helpers/files/imageidentify.js similarity index 100% rename from helpers/files/image-identify.js rename to helpers/files/imageidentify.js diff --git a/helpers/files/image-thumbnail.js b/helpers/files/imagethumbnail.js similarity index 100% rename from helpers/files/image-thumbnail.js rename to helpers/files/imagethumbnail.js diff --git a/helpers/files/file-check-mime-types.js b/helpers/files/mimetypes.js similarity index 100% rename from helpers/files/file-check-mime-types.js rename to helpers/files/mimetypes.js diff --git a/helpers/files/video-identify.js b/helpers/files/videoidentify.js similarity index 100% rename from helpers/files/video-identify.js rename to helpers/files/videoidentify.js diff --git a/helpers/files/video-thumbnail.js b/helpers/files/videothumbnail.js similarity index 100% rename from helpers/files/video-thumbnail.js rename to helpers/files/videothumbnail.js diff --git a/helpers/id-contrast.js b/helpers/id-contrast.js deleted file mode 100644 index 50232bbb..00000000 --- a/helpers/id-contrast.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = (hex) => { - const r = parseInt(hex.substr(0,2), 16); - const g = parseInt(hex.substr(2,2), 16); - const b = parseInt(hex.substr(4,2), 16) - return 0.375 * r + 0.5 * g + 0.125 * b; -} diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js index 82068c0f..3f31c91b 100644 --- a/helpers/paramconverter.js +++ b/helpers/paramconverter.js @@ -2,7 +2,7 @@ const Mongo = require(__dirname+'/../db/db.js') , allowedArrays = new Set(['checkedposts', 'globalcheckedposts', 'checkedbans', 'checkedbanners']) - , numberFields = ['reply_limit', 'max_files', 'thread_limit', 'thread'] + , numberFields = ['reply_limit', 'max_files', 'thread_limit', 'thread', 'min_message_length']; module.exports = (req, res, next) => { diff --git a/models/forms/actionhandler.js b/models/forms/actionhandler.js index cba84eab..d8a7c41b 100644 --- a/models/forms/actionhandler.js +++ b/models/forms/actionhandler.js @@ -3,16 +3,16 @@ const Posts = require(__dirname+'/../../db/posts.js') , Boards = require(__dirname+'/../../db/boards.js') , Mongo = require(__dirname+'/../../db/db.js') - , banPoster = require(__dirname+'/ban-poster.js') - , deletePosts = require(__dirname+'/delete-post.js') - , spoilerPosts = require(__dirname+'/spoiler-post.js') + , banPoster = require(__dirname+'/banposter.js') + , deletePosts = require(__dirname+'/deletepost.js') + , spoilerPosts = require(__dirname+'/spoilerpost.js') , stickyPosts = require(__dirname+'/stickyposts.js') , sagePosts = require(__dirname+'/sageposts.js') , lockPosts = require(__dirname+'/lockposts.js') , deletePostsFiles = require(__dirname+'/deletepostsfiles.js') - , reportPosts = require(__dirname+'/report-post.js') + , reportPosts = require(__dirname+'/reportpost.js') , globalReportPosts = require(__dirname+'/globalreportpost.js') - , dismissReports = require(__dirname+'/dismiss-report.js') + , dismissReports = require(__dirname+'/dismissreport.js') , dismissGlobalReports = require(__dirname+'/dismissglobalreport.js') , { buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../build.js'); diff --git a/models/forms/ban-poster.js b/models/forms/banposter.js similarity index 100% rename from models/forms/ban-poster.js rename to models/forms/banposter.js diff --git a/models/forms/delete-post.js b/models/forms/deletepost.js similarity index 100% rename from models/forms/delete-post.js rename to models/forms/deletepost.js diff --git a/models/forms/dismiss-report.js b/models/forms/dismissreport.js similarity index 100% rename from models/forms/dismiss-report.js rename to models/forms/dismissreport.js diff --git a/models/forms/make-post.js b/models/forms/makepost.js similarity index 94% rename from models/forms/make-post.js rename to models/forms/makepost.js index 9d158c96..3aa9fde3 100644 --- a/models/forms/make-post.js +++ b/models/forms/makepost.js @@ -22,12 +22,13 @@ const path = require('path') , permsCheck = require(__dirname+'/../../helpers/hasperms.js') , imageUpload = require(__dirname+'/../../helpers/files/imageupload.js') , videoUpload = require(__dirname+'/../../helpers/files/videoupload.js') - , fileCheckMimeType = require(__dirname+'/../../helpers/files/file-check-mime-types.js') - , imageThumbnail = require(__dirname+'/../../helpers/files/image-thumbnail.js') - , imageIdentify = require(__dirname+'/../../helpers/files/image-identify.js') - , videoThumbnail = require(__dirname+'/../../helpers/files/video-thumbnail.js') - , videoIdentify = require(__dirname+'/../../helpers/files/video-identify.js') - , formatSize = require(__dirname+'/../../helpers/files/format-size.js') + , fileCheckMimeType = require(__dirname+'/../../helpers/files/mimetypes.js') + , imageThumbnail = require(__dirname+'/../../helpers/files/imagethumbnail.js') + , imageIdentify = require(__dirname+'/../../helpers/files/imageidentify.js') + , videoThumbnail = require(__dirname+'/../../helpers/files/videothumbnail.js') + , videoIdentify = require(__dirname+'/../../helpers/files/videoidentify.js') + , formatSize = require(__dirname+'/../../helpers/files/formatsize.js') + , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js') , { buildCatalog, buildThread, buildBoard, buildBoardMultiple } = require(__dirname+'/../../build.js'); module.exports = async (req, res, next, numFiles) => { @@ -41,6 +42,7 @@ module.exports = async (req, res, next, numFiles) => { if (req.body.thread) { thread = await Posts.getPost(req.params.board, req.body.thread, true); if (!thread || thread.thread != null) { + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': 'Thread does not exist.', @@ -50,6 +52,7 @@ module.exports = async (req, res, next, numFiles) => { salt = thread.salt; redirect += `thread/${req.body.thread}.html` if (thread.locked && !hasPerms) { + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': 'Thread Locked', @@ -57,6 +60,7 @@ module.exports = async (req, res, next, numFiles) => { }); } if (thread.replyposts >= res.locals.board.settings.replyLimit) { //reply limit + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': 'Thread reached reply limit', @@ -65,6 +69,7 @@ module.exports = async (req, res, next, numFiles) => { } } if (numFiles > res.locals.board.settings.maxFiles) { + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': `Too many files. Max files per post is ${res.locals.board.settings.maxFiles}.`, @@ -77,6 +82,7 @@ module.exports = async (req, res, next, numFiles) => { // check all mime types befoer we try saving anything for (let i = 0; i < numFiles; i++) { if (!fileCheckMimeType(req.files.file[i].mimetype, {animatedImage: true, image: true, video: true})) { + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': `Invalid file type for ${req.files.file[i].name}. Mimetype ${req.files.file[i].mimetype} not allowed.`, diff --git a/models/forms/report-post.js b/models/forms/reportpost.js similarity index 100% rename from models/forms/report-post.js rename to models/forms/reportpost.js diff --git a/models/forms/spoiler-post.js b/models/forms/spoilerpost.js similarity index 100% rename from models/forms/spoiler-post.js rename to models/forms/spoilerpost.js diff --git a/models/forms/uploadbanners.js b/models/forms/uploadbanners.js index 88291d3e..47a76186 100644 --- a/models/forms/uploadbanners.js +++ b/models/forms/uploadbanners.js @@ -4,8 +4,8 @@ const path = require('path') , { remove, pathExists, ensureDir } = require('fs-extra') , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js') , imageUpload = require(__dirname+'/../../helpers/files/imageupload.js') - , fileCheckMimeType = require(__dirname+'/../../helpers/files/file-check-mime-types.js') - , imageIdentify = require(__dirname+'/../../helpers/files/image-identify.js') + , fileCheckMimeType = require(__dirname+'/../../helpers/files/mimetypes.js') + , imageIdentify = require(__dirname+'/../../helpers/files/imageidentify.js') , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js') , Boards = require(__dirname+'/../../db/boards.js') @@ -16,7 +16,7 @@ module.exports = async (req, res, next, numFiles) => { // check all mime types befoer we try saving anything for (let i = 0; i < numFiles; i++) { if (!fileCheckMimeType(req.files.file[i].mimetype, {image: true, animatedImage: true, video: false})) { - await deleteTempFiles(req.files.file) + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': `Invalid file type for ${req.files.file[i].name}. Mimetype ${req.files.file[i].mimetype} not allowed.`, @@ -37,13 +37,6 @@ module.exports = async (req, res, next, numFiles) => { if (exists) { await remove(file.tempFilePath); continue; -/* dont stop uploading the other banners just because one already exists. - return res.status(409).render('message', { - 'title': 'Conflict', - 'message': `Invalid file ${file.name}. Banner already exists.`, - 'redirect': redirect - }); -*/ } //add to list after checking it doesnt already exist @@ -61,7 +54,7 @@ module.exports = async (req, res, next, numFiles) => { //make sure its 300x100 banner if (geometry.width !== 300 || geometry.height !== 100) { - await deleteTempFiles(req.files.file); + await deleteTempFiles(req).catch(e => console.error); return res.status(400).render('message', { 'title': 'Bad request', 'message': `Invalid file ${file.name}. Banners must be 300x100.`,