From 52c189a15344a5d5c00137df68af2e6f4c05e74e Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Fri, 11 Jun 2021 08:29:59 +0000 Subject: [PATCH] allow board custom pages to be edited, like newspost editing because rewriting the whole page can be annoying and you couldnt access the text without styling also can change .html name, maybe that will get removed but it works atm. still needs more tweaks and proper testing --- CHANGELOG.md | 1 + controllers/forms.js | 3 +- controllers/forms/editcustompage.js | 62 +++++++++++++++++++++ controllers/forms/index.js | 1 + controllers/pages.js | 13 +++-- db/custompages.js | 26 ++++++++- models/forms/addcustompage.js | 2 +- models/forms/editcustompage.js | 58 +++++++++++++++++++ models/pages/{ => globalmanage}/editnews.js | 2 +- models/pages/globalmanage/index.js | 1 + models/pages/index.js | 1 - models/pages/manage/editcustompage.js | 26 +++++++++ models/pages/manage/index.js | 1 + views/mixins/custompage.pug | 6 ++ views/mixins/newspost.pug | 2 +- views/pages/editcustompage.pug | 27 +++++++++ views/pages/managecustompages.pug | 2 +- 17 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 controllers/forms/editcustompage.js create mode 100644 models/forms/editcustompage.js rename models/pages/{ => globalmanage}/editnews.js (85%) create mode 100644 models/pages/manage/editcustompage.js create mode 100644 views/pages/editcustompage.pug diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b5099b..5611a1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,3 +32,4 @@ - Try to fallback thumbnail generation for video with horribly broken encoding - Country blocklist now can actually fit all countries - Make "auth level" text box into "account type" dropdown in accounts page, easier to understand + - Board owners can now edit custom pages diff --git a/controllers/forms.js b/controllers/forms.js index 5bf043ba..8bb6ca44 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -28,7 +28,7 @@ const express = require('express') addFlagsController, deleteFlagsController, boardSettingsController, transferController, resignController, deleteAccountController, loginController, registerController, changePasswordController, editAccountsController, globalSettingsController, createBoardController, makePostController, - editPostController, newCaptcha, blockBypass, logout } = require(__dirname+'/forms/index.js'); + editCustomPageController, editPostController, newCaptcha, blockBypass, logout } = require(__dirname+'/forms/index.js'); //make new post @@ -57,6 +57,7 @@ router.post('/board/:board/addcustompages', useSession, sessionRefresh, csrf, Bo router.post('/board/:board/deletecustompages', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), deleteCustomPageController.paramConverter, deleteCustomPageController.controller); //delete banners router.post('/board/:board/editbans', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(3), editBansController.paramConverter, editBansController.controller); //edit bans router.post('/board/:board/deleteboard', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(config.get.deleteBoardPermLevel), deleteBoardController.controller); //delete board +router.post('/board/:board/editcustompage', useSession, sessionRefresh, csrf, Boards.exists, calcPerms, isLoggedIn, hasPerms(2), editCustomPageController.paramConverter, editCustomPageController.controller); //edit custom page //global management forms router.post('/global/editbans', useSession, sessionRefresh, csrf, calcPerms, isLoggedIn, hasPerms(1), editBansController.paramConverter, editBansController.controller); //remove bans diff --git a/controllers/forms/editcustompage.js b/controllers/forms/editcustompage.js new file mode 100644 index 00000000..d685f42c --- /dev/null +++ b/controllers/forms/editcustompage.js @@ -0,0 +1,62 @@ +'use strict'; + +const editCustomPage = require(__dirname+'/../../models/forms/editcustompage.js') + , { CustomPages } = require(__dirname+'/../../db/') + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , paramConverter = require(__dirname+'/../../helpers/paramconverter.js') + , { checkSchema, lengthBody, numberBody, minmaxBody, numberBodyVariable, + inArrayBody, arrayInBody, existsBody } = require(__dirname+'/../../helpers/schema.js') + , config = require(__dirname+'/../../config.js'); + +module.exports = { + + paramConverter: paramConverter({ + trimFields: ['message', 'title', 'page'], + processMessageLength: true, + objectIdFields: ['page_id'], + }), + + controller: async (req, res, next) => { + + const { globalLimits } = config.get; + + const errors = await checkSchema([ + { result: existsBody(req.body.page_id), expected: true, error: 'Missing page id' }, + { result: existsBody(req.body.message), expected: true, error: 'Missing message' }, + { result: existsBody(req.body.title), expected: true, error: 'Missing title' }, + { result: existsBody(req.body.page), expected: true, error: 'Missing .html name' }, + { result: () => { + if (req.body.page) { + return /[a-z0-9_-]+/.test(req.body.page); + } + return false; + } , expected: true, error: '.html name must contain a-z 0-9 _ - only' }, + { result: numberBody(res.locals.messageLength, 0, globalLimits.customPages.maxLength), expected: true, error: `Message must be ${globalLimits.customPages.maxLength} characters or less` }, + { result: lengthBody(req.body.title, 0, 50), expected: false, error: 'Title must be 50 characters or less' }, + { result: lengthBody(req.body.page, 0, 50), expected: false, error: '.html name must be 50 characters or less' }, + { result: async () => { + const existingPage = await CustomPages.findOne(req.params.board, req.body.page); + if (existingPage && existingPage.page === req.body.page) { + return existingPage._id === req.body.page_id; + } + return true; + }, expected: true, error: '.html name must be unique'}, + ]); + + if (errors.length > 0) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': errors, + 'redirect': req.headers.referer || '/${req.params.board}/manage/custompages.html' + }); + } + + try { + await editCustomPage(req, res, next); + } catch (err) { + return next(err); + } + + } + +} diff --git a/controllers/forms/index.js b/controllers/forms/index.js index cccef6be..31c9c80e 100644 --- a/controllers/forms/index.js +++ b/controllers/forms/index.js @@ -11,6 +11,7 @@ module.exports = { deleteCustomPageController: require(__dirname+'/deletecustompage.js'), addNewsController: require(__dirname+'/addnews.js'), editNewsController: require(__dirname+'/editnews.js'), + editCustomPageController: require(__dirname+'/editcustompage.js'), deleteNewsController: require(__dirname+'/deletenews.js'), uploadBannersController: require(__dirname+'/uploadbanners.js'), deleteBannersController: require(__dirname+'/deletebanners.js'), diff --git a/controllers/pages.js b/controllers/pages.js index b86f2dc1..d8f8991f 100644 --- a/controllers/pages.js +++ b/controllers/pages.js @@ -16,16 +16,17 @@ const express = require('express') , csrf = require(__dirname+'/../helpers/checks/csrfmiddleware.js') , setMinimal = require(__dirname+'/../helpers/setminimal.js') //page models - , { manageRecent, manageReports, manageAssets, manageSettings, manageBans, + , { manageRecent, manageReports, manageAssets, manageSettings, manageBans, editCustomPage, manageBoard, manageThread, manageLogs, manageCatalog, manageCustomPages } = require(__dirname+'/../models/pages/manage/') - , { globalManageSettings, globalManageReports, globalManageBans, globalManageBoards, + , { globalManageSettings, globalManageReports, globalManageBans, globalManageBoards, editNews, globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs } = require(__dirname+'/../models/pages/globalmanage/') - , { changePassword, blockBypass, home, register, login, create, editNews, + , { changePassword, blockBypass, home, register, login, create, board, catalog, banners, randombanner, news, captchaPage, overboard, overboardCatalog, captcha, thread, modlog, modloglist, account, boardlist, customPage } = require(__dirname+'/../models/pages/') , threadParamConverter = paramConverter({ processThreadIdParam: true }) , logParamConverter = paramConverter({ processDateParam: true }) - , newsParamConverter = paramConverter({ objectIdParams: ['newsid'] }); + , newsParamConverter = paramConverter({ objectIdParams: ['newsid'] }) + , custompageParamConverter = paramConverter({ objectIdParams: ['custompageid'] }); //homepage router.get('/index.html', home); @@ -73,9 +74,9 @@ router.get('/globalmanage/accounts.html', useSession, sessionRefresh, isLoggedIn router.get('/globalmanage/settings.html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, globalManageSettings); //edit pages -router.get('/editnews/:newsid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, newsParamConverter, editNews); +router.get('/globalmanage/editnews/:newsid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, calcPerms, hasPerms(0), csrf, newsParamConverter, editNews); +router.get('/:board/manage/editcustompage/:custompageid([a-f0-9]{24}).html', useSession, sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, custompageParamConverter, editCustomPage); //TODO: edit post get endpoint -//TODO: edit board custom page get endpoint //captcha router.get('/captcha', geoAndTor, processIp, captcha); //get captcha image and cookie diff --git a/db/custompages.js b/db/custompages.js index 01f738ea..4aae22e2 100644 --- a/db/custompages.js +++ b/db/custompages.js @@ -18,6 +18,7 @@ module.exports = { .toArray(); }, + //browsing board findOne: (board, page) => { return db.findOne({ 'board': board, @@ -25,6 +26,14 @@ module.exports = { }); }, + //editing + findOneId: (id, board) => { + return db.findOne({ + '_id': id, + 'board': board, + }); + }, + boardCount: (board) => { return db.countDocuments({ 'board': board, @@ -35,7 +44,22 @@ module.exports = { return db.insertOne(custompage); }, - updateOne: () => {}, + findOneAndUpdate: (id, board, page, title, raw, markdown, edited) => { + return db.findOneAndUpdate({ + '_id': id, + 'board': board, + }, { + '$set': { + 'page': page, + 'title': title, + 'message.raw': raw, + 'message.markdown': markdown, + 'edited': edited, + } + }, { + returnDocument: 'before', + }); + }, deleteMany: (pages, board) => { return db.deleteMany({ diff --git a/models/forms/addcustompage.js b/models/forms/addcustompage.js index b507528c..7d00127a 100644 --- a/models/forms/addcustompage.js +++ b/models/forms/addcustompage.js @@ -20,7 +20,7 @@ module.exports = async (req, res, next) => { 'markdown': markdownMessage }, 'date': new Date(), - 'edited': null, //unused currently + 'edited': null, }; const insertedCustomPage = await CustomPages.insertOne(post); diff --git a/models/forms/editcustompage.js b/models/forms/editcustompage.js new file mode 100644 index 00000000..0e34c549 --- /dev/null +++ b/models/forms/editcustompage.js @@ -0,0 +1,58 @@ +'use strict'; + +const { CustomPages } = require(__dirname+'/../../db/') + , uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js') + , { remove } = require('fs-extra') + , dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') + , buildQueue = require(__dirname+'/../../queue.js') + , { prepareMarkdown } = require(__dirname+'/../../helpers/posting/markdown.js') + , messageHandler = require(__dirname+'/../../helpers/posting/message.js'); + +module.exports = async (req, res, next) => { + + const message = prepareMarkdown(req.body.message, false); + const { message: markdownPage } = await messageHandler(message, null, null, res.locals.permLevel); + const editedDate = new Date(); + + const oldPage = await CustomPages.findOneAndUpdate(req.body.page_id, req.params.board, + req.body.page, req.body.title, message, markdownPage, editedDate).then(res => res.value); + + if (oldPage === null) { + return dynamicResponse(req, res, 400, 'message', { + 'title': 'Bad request', + 'errors': 'Custom page does not exist', + 'redirect': req.headers.referer || '/${req.params.board}/manage/custompages.html' + }); + } + + await remove(`${uploadDirectory}/html/${req.params.board}/custompage/${oldPage.page}.html`); + + const newPage = { + '_id': oldPage._id, + 'board': req.params.board, + 'page': req.body.page, + 'title': req.body.title, + 'message': { + 'raw': message, + 'markdown': markdownPage + }, + 'date': oldPage.date, + 'edited': editedDate, + }; + + buildQueue.push({ + 'task': 'buildCustomPage', + 'options': { + 'board': res.locals.board, + 'page': newPage.page, + 'customPage': newPage, + } + }); + + return dynamicResponse(req, res, 200, 'message', { + 'title': 'Success', + 'message': 'Updated custom page', + 'redirect': `/${req.params.board}/manage/custompages.html`, + }); + +} diff --git a/models/pages/editnews.js b/models/pages/globalmanage/editnews.js similarity index 85% rename from models/pages/editnews.js rename to models/pages/globalmanage/editnews.js index 7b7c8530..6968cd09 100644 --- a/models/pages/editnews.js +++ b/models/pages/globalmanage/editnews.js @@ -1,6 +1,6 @@ 'use strict'; -const { News } = require(__dirname+'/../../db/'); +const { News } = require(__dirname+'/../../../db/'); module.exports = async (req, res, next) => { diff --git a/models/pages/globalmanage/index.js b/models/pages/globalmanage/index.js index 02cc8a84..3d536d7e 100644 --- a/models/pages/globalmanage/index.js +++ b/models/pages/globalmanage/index.js @@ -9,4 +9,5 @@ module.exports = { globalManageNews: require(__dirname+'/news.js'), globalManageAccounts: require(__dirname+'/accounts.js'), globalManageSettings: require(__dirname+'/settings.js'), + editNews: require(__dirname+'/editnews.js'), } diff --git a/models/pages/index.js b/models/pages/index.js index 564d292c..3db7b42b 100644 --- a/models/pages/index.js +++ b/models/pages/index.js @@ -22,5 +22,4 @@ module.exports = { boardlist: require(__dirname+'/boardlist.js'), overboard: require(__dirname+'/overboard.js'), overboardCatalog: require(__dirname+'/overboardcatalog.js'), - editNews: require(__dirname+'/editnews.js'), } diff --git a/models/pages/manage/editcustompage.js b/models/pages/manage/editcustompage.js new file mode 100644 index 00000000..47cefeff --- /dev/null +++ b/models/pages/manage/editcustompage.js @@ -0,0 +1,26 @@ +'use strict'; + +const { CustomPages } = require(__dirname+'/../../../db/'); + +module.exports = async (req, res, next) => { + + let customPage; + try { + customPage = await CustomPages.findOneId(req.params.custompageid, req.params.board); + } catch (err) { + return next(err) + } + + if (!customPage) { + return next(); + } + + res + .set('Cache-Control', 'private, max-age=5') + .render('editcustompage', { + csrf: req.csrfToken(), + page: customPage, + board: res.locals.board, + }); + +} diff --git a/models/pages/manage/index.js b/models/pages/manage/index.js index 1d321cba..739192d6 100644 --- a/models/pages/manage/index.js +++ b/models/pages/manage/index.js @@ -11,4 +11,5 @@ module.exports = { manageCatalog: require(__dirname+'/catalog.js'), manageThread: require(__dirname+'/thread.js'), manageCustomPages: require(__dirname+'/custompages.js'), + editCustomPage: require(__dirname+'/editcustompage.js'), } diff --git a/views/mixins/custompage.pug b/views/mixins/custompage.pug index 488f0474..eff0c381 100644 --- a/views/mixins/custompage.pug +++ b/views/mixins/custompage.pug @@ -6,6 +6,7 @@ mixin custompage(page, manage=false) if manage === true input.left.post-check(type='checkbox', name='checkedcustompages' value=page.page) a.left(href=`/${board._id}/custompage/${page.page}.html`) #{page.title} + a.right.ml-5(href=`/${board._id}/manage/editcustompage/${page._id}.html`) [Edit] - const pageDate = new Date(page.date); time.right.reltime(datetime=pageDate.toISOString()) #{pageDate.toLocaleString(undefined, {hourCycle:'h23'})} tr @@ -14,3 +15,8 @@ mixin custompage(page, manage=false) p.no-m-p #{`${page.message.raw.substring(0,50)}...`} else pre.post-message.no-m-p !{page.message.markdown} + if page.edited + small.right.cb.edited + | Last edited + - const pageEditDate = new Date(page.edited); + time.reltime(datetime=pageEditDate.toISOString()) #{pageEditDate.toLocaleString(undefined, {hourCycle:'h23'})} diff --git a/views/mixins/newspost.pug b/views/mixins/newspost.pug index 86e1da91..338d5cb9 100644 --- a/views/mixins/newspost.pug +++ b/views/mixins/newspost.pug @@ -8,7 +8,7 @@ mixin newspost(post, globalmanage=false) input.left.post-check(type='checkbox', name='checkednews' value=post._id) a.left(href=`#${post._id}`) #{post.title} if globalmanage === true - a.right.ml-5(href=`/editnews/${post._id}.html`) [Edit] + a.right.ml-5(href=`/globalmanage/editnews/${post._id}.html`) [Edit] - const newsDate = new Date(post.date); time.right.reltime(datetime=newsDate.toISOString()) #{newsDate.toLocaleString(undefined, {hourCycle:'h23'})} tr diff --git a/views/pages/editcustompage.pug b/views/pages/editcustompage.pug new file mode 100644 index 00000000..50be4831 --- /dev/null +++ b/views/pages/editcustompage.pug @@ -0,0 +1,27 @@ +extends ../layout.pug + +block head + title Edit Custom Page + +block content + h1.board-title Edit Custom Page + include ../includes/stickynav.pug + .form-wrapper.flex-center.mv-10 + form.form-post(action=`/forms/board/${board._id}/editcustompage` method='POST') + input(type='hidden' name='_csrf' value=csrf) + input(type='hidden' name='page_id' value=page._id) + .row + .label .html name + input(type='text' name='page' pattern='[a-z0-9-_]+' title='a-z0-9-_ only' value=page.page required) + .table-container.flex-center.mv-5 + table + tr + th + input.edit.left(type='text' name='title' value=page.title required) + - const pageDate = new Date(page.date); + time.right.reltime(datetime=pageDate.toISOString()) #{pageDate.toLocaleString(undefined, {hourCycle:'h23'})} + tr + td + + textarea.edit.fw(name='message' rows='10' placeholder='Supports post styling' required) #{page.message.raw} + input(type='submit', value='save') diff --git a/views/pages/managecustompages.pug b/views/pages/managecustompages.pug index d507c8d5..12096c9f 100644 --- a/views/pages/managecustompages.pug +++ b/views/pages/managecustompages.pug @@ -27,7 +27,7 @@ block content input(type='submit', value='submit') if customPages.length > 0 hr(size=1) - h4.no-m-p Delete Custom Pages: + h4.no-m-p Manage Custom Pages: .form-wrapper.flexleft form.form-post(action=`/forms/board/${board._id}/deletecustompages`, enctype='application/x-www-form-urlencoded', method='POST') input(type='hidden' name='_csrf' value=csrf)