diff --git a/controllers/forms.js b/controllers/forms.js index 32027f5d..d6a0152d 100644 --- a/controllers/forms.js +++ b/controllers/forms.js @@ -7,6 +7,7 @@ const express = require('express') , Trips = require(__dirname+'/../db-models/trips.js') , makePost = require(__dirname+'/../models/forms/make-post.js') , deletePost = require(__dirname+'/../models/forms/delete-post.js') + , reportPost = require(__dirname+'/../models/forms/report-post.js') , loginAccount = require(__dirname+'/../models/forms/login.js') , registerAccount = require(__dirname+'/../models/forms/register.js') , numberConverter = require(__dirname+'/../helpers/number-converter.js'); @@ -132,16 +133,22 @@ router.post('/board/:board', Boards.exists, numberConverter, (req, res, next) => }); -// delete post(s) -router.post('/board/:board/delete', Boards.exists, numberConverter, (req, res, next) => { +//report, delete, sticky, etc +router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, next) => { const errors = []; if (req.body.password && req.body.password.length > 50) { - errors.push('Password must be 50 characters or less') + errors.push('Password must be 50 characters or less'); + } + if (req.body.report && req.body.report.length > 50) { + errors.push('Report must be 50 characters or less'); + } + if (req.body.password && req.body.report) { + errors.push('Can only report or delete, not both'); } if (!req.body.checked || req.body.checked.length === 0 || req.body.checked.length > 10) { //10 for now just for _some_ limit - errors.push('Must check 1-10 boxes for posts to delete') + errors.push('Must select 1-10 posts') } if (errors.length > 0) { @@ -152,7 +159,15 @@ router.post('/board/:board/delete', Boards.exists, numberConverter, (req, res, n }) } - deletePost(req, res); + //we checked to make sure there are not both, so... + if (req.body.report) { + //if theres a report reason, handle reports + reportPost(req, res); + } else { + //otherwise, must be delete request which + //for authed users DOES NOT requoie passwrd + deletePost(req, res); + } }); diff --git a/db-models/posts.js b/db-models/posts.js index 1537a6d0..418023b6 100644 --- a/db-models/posts.js +++ b/db-models/posts.js @@ -17,7 +17,8 @@ module.exports = { },{ 'projection': { 'salt': 0, - 'password': 0 + 'password': 0, + 'reports': 0 } }).sort({ 'bumped': -1 @@ -31,6 +32,7 @@ module.exports = { 'projection': { 'salt': 0, 'password': 0, + 'reports': 0 } }).sort({ '_id': -1 @@ -42,7 +44,7 @@ module.exports = { }, - getPages: async (board) => { + getPages: (board) => { return db.collection(board).estimatedDocumentCount(); }, @@ -55,7 +57,8 @@ module.exports = { }, { 'projection': { 'salt': 0, - 'password': 0 + 'password': 0, + 'reports': 0 } }), module.exports.getThreadPosts(board, id) @@ -71,7 +74,7 @@ module.exports = { }, - getThreadPosts: async(board, id) => { + getThreadPosts: (board, id) => { // all posts within a thread return db.collection(board).find({ @@ -79,7 +82,8 @@ module.exports = { }, { 'projection': { 'salt': 0 , - 'password': 0 + 'password': 0, + 'reports': 0 } }).sort({ '_id': 1 @@ -87,7 +91,7 @@ module.exports = { }, - getCatalog: async (board) => { + getCatalog: (board) => { // get all threads for catalog return db.collection(board).find({ @@ -95,13 +99,14 @@ module.exports = { }, { 'projection': { 'salt': 0, - 'password': 0 + 'password': 0, + 'reports': 0 } }).toArray(); }, - getPost: async (board, id, admin) => { + getPost: (board, id, admin) => { // get a post if (admin) { @@ -115,14 +120,15 @@ module.exports = { }, { 'projection': { 'salt': 0, - 'password': 0 + 'password': 0, + 'reports': 0 } }); }, //takes array "ids" of post ids - getPosts: async(board, ids, admin) => { + getPosts: (board, ids, admin) => { if (admin) { return db.collection(board).find({ @@ -139,7 +145,8 @@ module.exports = { }, { 'projection': { 'salt': 0, - 'password': 0 + 'password': 0, + 'reports': 0 } }).toArray(); @@ -169,11 +176,36 @@ module.exports = { }, - deleteOne: async (board, options) => { + reportMany: (board, ids, report) => { + return db.collection(board).updateMany({ + '_id': { + '$in': ids + } + }, { + '$push': { + 'reports': report + } + }); + }, + + getReports: (board) => { + return db.collection(board).find({ + 'reports.0': { + '$exists': true + } + }, { + 'projection': { + 'salt': 0, + 'password': 0, + } + }).toArray(); + }, + + deleteOne: (board, options) => { return db.collection(board).deleteOne(options); }, - deleteMany: async (board, ids) => { + deleteMany: (board, ids) => { return db.collection(board).deleteMany({ '_id': { @@ -183,7 +215,7 @@ module.exports = { }, - deleteAll: async (board) => { + deleteAll: (board) => { return db.collection(board).deleteMany({}); }, diff --git a/models/forms/edit-post.js b/models/forms/edit-post.js new file mode 100644 index 00000000..4960a5fd --- /dev/null +++ b/models/forms/edit-post.js @@ -0,0 +1,34 @@ +'use strict'; + +const uuidv4 = require('uuid/v4') + , path = require('path') + , Posts = require(__dirname+'/../../db-models/posts.js') + +module.exports = async (req, res, numFiles) => { + + // get the post that we are trying to edit + let post; + try { + post = await Posts.getPost(req.params.board, req.body.id, true); + } catch (err) { + console.error(err); + return res.status(500).render('error'); + } + if (!thread || thread.thread != null) { + return res.status(400).render('message', { + 'title': 'Bad request', + 'message': 'Post does not exist.', + 'redirect': redirect + }); + } + + // sticky, lock, sage, spoiler, etc + for (let i = 0; i < req.body.actions.length; i++) { + + } + + const post = await Posts.updateOne(req.params.board, data) + const successRedirect = `/${req.params.board}/thread/${req.body.thread || post.insertedId}`; + + return res.redirect(successRedirect); +} diff --git a/models/forms/make-post.js b/models/forms/make-post.js index 6242763d..8db35f92 100644 --- a/models/forms/make-post.js +++ b/models/forms/make-post.js @@ -164,6 +164,7 @@ module.exports = async (req, res, numFiles) => { 'userId': userId, 'files': files, 'salt': salt, + 'reports': [] }; const post = await Posts.insertOne(req.params.board, data) diff --git a/models/forms/report-post.js b/models/forms/report-post.js new file mode 100644 index 00000000..e8425a23 --- /dev/null +++ b/models/forms/report-post.js @@ -0,0 +1,22 @@ +'use strict'; + +const Posts = require(__dirname+'/../../db-models/posts.js'); + +module.exports = async (req, res) => { + + try { + //push the report to all checked posts + await Posts.reportMany(req.params.board, req.body.checked, req.body.report); + } catch (err) { + console.error(err); + return res.status(500).render('error'); + } + + //hooray! + return res.render('message', { + 'title': 'Success', + 'message': `Reported post(s) successfully`, + 'redirect': `/${req.params.board}` + }); + +} diff --git a/models/pages/manage.js b/models/pages/manage.js index a8d685eb..4a1de412 100644 --- a/models/pages/manage.js +++ b/models/pages/manage.js @@ -1,10 +1,21 @@ 'use strict'; -module.exports = (req, res) => { +const Posts = require(__dirname+'/../../db-models/posts.js'); + +module.exports = async (req, res) => { + + let posts; + try { + posts = await Posts.getReports(req.params.board); + } catch (err) { + console.error(err); + return res.status(500).render('error'); + } //render the page res.render('manage', { - csrf: req.csrfToken() + csrf: req.csrfToken(), + posts: posts }); } diff --git a/static/css/style.css b/static/css/style.css index 98206289..2b32a69f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -9,6 +9,13 @@ body { margin: 0; } +.reports { + background: #fca!important; + border-color: #c97!important; + border-width: 1px 0; + border-style: solid none; +} + .redtext { color: maroon; } @@ -49,7 +56,7 @@ input, textarea { .delete-wrapper { align-items: center; - flex-direction: row; + /*flex-direction: row;*/ } .form-post { @@ -129,7 +136,7 @@ input textarea { max-width: 100%; } -.post-container:target { +.post-container:target, .op:target { outline: 1px dashed blue; outline-offset: -1px; } @@ -214,6 +221,8 @@ th, td { hr { color: lightgray; + /*border-top: 1px solid black; + background: lightgray;*/ } @media only screen and (max-width: 800px) { diff --git a/views/includes/deletefooter.pug b/views/includes/deletefooter.pug new file mode 100644 index 00000000..7b965d78 --- /dev/null +++ b/views/includes/deletefooter.pug @@ -0,0 +1,5 @@ +section.delete-wrapper + p Report OR delete selected posts + input#report(type='report', name='report', placeholder='report reason' autocomplete='off') + input#password(type='password', name='password', placeholder='password (for deletion)' autocomplete='off') + input(type='submit', value='submit') diff --git a/views/includes/navbar.pug b/views/includes/navbar.pug index ae2ef1ba..9d77b7a0 100644 --- a/views/includes/navbar.pug +++ b/views/includes/navbar.pug @@ -1,3 +1,5 @@ nav.navbar a.nav-item(href='/') Home a.nav-item(href='/login') Login + if board + a.nav-item(href=`/${board._id}/manage`) Manage Board diff --git a/views/mixins/post.pug b/views/mixins/post.pug index 3bbac495..8d2642db 100644 --- a/views/mixins/post.pug +++ b/views/mixins/post.pug @@ -7,10 +7,7 @@ mixin post(board, post, truncate) span.post-name #{post.name} span #{post.date.toLocaleString()} span.user-id(style=`background: #${post.userId}`) #{post.userId} - if post.thread == null - span: a(href=`/${board._id}/thread/${post._id}`) ##{post._id} - else - span: a(href=`/${board._id}/thread/${post.thread}#${post._id}`) ##{post._id} + span: a(href=`/${board._id}/thread/${post.thread ? post.thread : post._id}#${post._id}`) ##{post._id} if post.files.length > 0 .post-files each file in post.files @@ -40,4 +37,6 @@ mixin post(board, post, truncate) blockquote.post-message !{post.message} else blockquote.post-message !{post.message} - + if post.reports + each report in post.reports + span.reports.post-container #{report} diff --git a/views/pages/board.pug b/views/pages/board.pug index 9730bae9..b2459939 100644 --- a/views/pages/board.pug +++ b/views/pages/board.pug @@ -9,13 +9,13 @@ block content h4.board-description #{board.description} include ../includes/postform.pug hr(size=1) - form(action='/forms/board/'+board._id+'/delete' method='POST' enctype='application/x-www-form-urlencoded') + form(action='/forms/board/'+board._id+'/posts' method='POST' enctype='application/x-www-form-urlencoded') input(type='hidden' name='_csrf' value=csrf) if threads.length === 0 p No posts. hr(size=1) for thread in threads - section.thread(id=thread._id) + section.thread +post(board, thread, true) for post in thread.replies +post(board, post, true) @@ -25,6 +25,4 @@ block content - for(let i = 0; i < pages; i++) span: a(href=`/${board._id}/${i+1}`) #{i+1} hr(size=1) - section.delete-wrapper - input#password(type='password', name='password', placeholder='password (for deletion)' autocomplete='off') - input(type='submit', value='delete') + include ../includes/deletefooter.pug diff --git a/views/pages/manage.pug b/views/pages/manage.pug index 709b3026..0017e126 100644 --- a/views/pages/manage.pug +++ b/views/pages/manage.pug @@ -1,7 +1,22 @@ extends ../layout.pug +include ../mixins/post.pug block head title Login block content - p dummy manage page + h1.board-title /#{board._id}/ - #{board.name} + h4.board-description Management Panel + hr(size=1) + form(action='/forms/board/'+board._id+'/posts' method='POST' enctype='application/x-www-form-urlencoded') + input(type='hidden' name='_csrf' value=csrf) + if posts.length === 0 + p No posts. + hr(size=1) + for post in posts + section.thread + +post(board, post) + hr(size=1) + section.delete-wrapper + input(type='submit', value='delete') + diff --git a/views/pages/message.pug b/views/pages/message.pug index 7fbd79b6..dab4ab0c 100644 --- a/views/pages/message.pug +++ b/views/pages/message.pug @@ -1,7 +1,7 @@ extends ../layout.pug block head - meta(http-equiv="refresh" content=`6;url=${redirect}`) + meta(http-equiv="refresh" content=`3;url=${redirect}`) block content h1 #{title} diff --git a/views/pages/thread.pug b/views/pages/thread.pug index de79b084..a1d62c60 100644 --- a/views/pages/thread.pug +++ b/views/pages/thread.pug @@ -14,16 +14,14 @@ block content hr(size=1) include ../includes/postform.pug hr(size=1) - form(action='/forms/board/'+board._id+'/delete' method='POST' enctype='application/x-www-form-urlencoded') + form(action='/forms/board/'+board._id+'/posts' method='POST' enctype='application/x-www-form-urlencoded') input(type='hidden' name='_csrf' value=csrf) - section.thread(id=thread._id) + section.thread +post(board, thread) for post in thread.replies +post(board, post) hr(size=1) - section.delete-wrapper - input#password(type='password', name='password', placeholder='password (for deletion)' autocomplete='off') - input(type='submit', value='delete') + include ../includes/deletefooter.pug diff --git a/wipe.js b/wipe.js index 4482bda2..5ff5b094 100644 --- a/wipe.js +++ b/wipe.js @@ -44,6 +44,24 @@ const Mongo = require(__dirname+'/helpers/db.js') await Posts.db.collection('b').createIndex({"bumped": 1}); await Posts.db.collection('pol').createIndex({"thread": 1}); await Posts.db.collection('pol').createIndex({"bumped": 1}); + await Posts.db.collection('pol').createIndex({ + 'reports.0': 1 + }, { + partialFilterExpression: { + 'reports.0': { + '$exists': true + } + } + }); + await Posts.db.collection('b').createIndex({ + 'reports.0': 1 + }, { + partialFilterExpression: { + 'reports.0': { + '$exists': true + } + } + }); await readdir('static/img/').then(async files => { await Promise.all(files.map(async file => { unlink(path.join('static/img/', file));