banner uploads. no listing or deleting yet. gotta sleep

merge-requests/208/head
fatchan 5 years ago
parent b0f09621b9
commit 92f8e993d9
  1. 37
      controllers/forms.js
  2. 404
      controllers/forms.js.save.1
  3. 14
      db/boards.js
  4. 15
      helpers/files/deletefailed.js
  5. 11
      helpers/files/file-check-mime-types.js
  6. 3
      models/forms/make-post.js
  7. 62
      models/forms/uploadbanners.js
  8. 1
      models/pages/board.js
  9. 2
      server.js
  10. 10
      static/css/style.css
  11. 14
      views/includes/bannerform.pug
  12. 2
      views/includes/boardheader.pug
  13. 3
      views/includes/navbar.pug
  14. 3
      views/mixins/post.pug
  15. 1
      views/pages/manage.pug
  16. 4
      wipe.js

@ -9,6 +9,7 @@ const express = require('express')
, 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')
, deletePosts = require(__dirname+'/../models/forms/delete-post.js')
, spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js')
, reportPosts = require(__dirname+'/../models/forms/report-post.js')
@ -145,6 +146,42 @@ router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, asyn
});
//upload banners
router.post('/board/:board/addbanners', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
if (Array.isArray(req.files.file)) {
numFiles = req.files.file.length;
} else {
numFiles = 1;
req.files.file = [req.files.file];
}
}
const errors = [];
if (numFiles === 0) {
errors.push('Must provide a file');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage`
})
}
try {
await uploadBanners(req, res, next, numFiles);
} catch (err) {
console.error(err);
return next(err);
}
});
//report/delete/spoiler/ban
router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, async (req, res, next) => {

@ -0,0 +1,404 @@
'use strict';
const express = require('express')
, router = express.Router()
, Boards = require(__dirname+'/../db/boards.js')
, Posts = require(__dirname+'/../db/posts.js')
, Trips = require(__dirname+'/../db/trips.js')
, Bans = require(__dirname+'/../db/bans.js')
, banPoster = require(__dirname+'/../models/forms/ban-poster.js')
, removeBans = require(__dirname+'/../models/forms/removebans.js')
, makePost = require(__dirname+'/../models/forms/make-post.js')
, deletePosts = require(__dirname+'/../models/forms/delete-post.js')
, spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js')
, reportPosts = require(__dirname+'/../models/forms/report-post.js')
, globalReportPosts = require(__dirname+'/../models/forms/globalreportpost.js')
, dismissReports = require(__dirname+'/../models/forms/dismiss-report.js')
, dismissGlobalReports = require(__dirname+'/../models/forms/dismissglobalreport.js')
, loginAccount = require(__dirname+'/../models/forms/login.js')
, registerAccount = require(__dirname+'/../models/forms/register.js')
, hasPerms = require(__dirname+'/../helpers/haspermsmiddleware.js')
, numberConverter = require(__dirname+'/../helpers/number-converter.js')
, banCheck = require(__dirname+'/../helpers/bancheck.js');
// login to account
router.post('/login', (req, res, next) => {
const errors = [];
//check exist
if (!req.body.username || req.body.username.length <= 0) {
errors.push('Missing username');
}
if (!req.body.password || req.body.password.length <= 0) {
errors.push('Missing password');
}
//check too long
if (req.body.username && req.body.username.length > 50) {
errors.push('Username must be 50 characters or less');
}
if (req.body.password && req.body.password.length > 100) {
errors.push('Password must be 100 characters or less');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/login'
})
}
loginAccount(req, res, next);
});
//register account
router.post('/register', (req, res, next) => {
const errors = [];
//check exist
if (!req.body.username || req.body.username.length <= 0) {
errors.push('Missing username');
}
if (!req.body.password || req.body.password.length <= 0) {
errors.push('Missing password');
}
if (!req.body.passwordconfirm || req.body.passwordconfirm.length <= 0) {
errors.push('Missing password confirmation');
}
//check too long
if (req.body.username && req.body.username.length > 50) {
errors.push('Username must be 50 characters or less');
}
if (req.body.password && req.body.password.length > 100) {
errors.push('Password must be 100 characters or less');
}
if (req.body.passwordconfirm && req.body.passwordconfirm.length > 100) {
errors.push('Password confirmation must be 100 characters or less');
}
if (req.body.password != req.body.passwordconfirm) {
errors.push('Password and password confirmation must match');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/register'
})
}
registerAccount(req, res, next);
});
// make new post
router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
if (Array.isArray(req.files.file)) {
numFiles = req.files.file.length;
} else {
numFiles = 1;
req.files.file = [req.files.file];
}
}
const errors = [];
if (!req.body.message && numFiles === 0) {
errors.push('Must provide a message or file');
}
if (req.body.message && req.body.message.length > 2000) {
errors.push('Message must be 2000 characters or less');
}
if (!req.body.thread && (!req.body.message || req.body.message.length === 0)) {
errors.push('Threads must include a message');
}
if (req.body.name && req.body.name.length > 50) {
errors.push('Name must be 50 characters or less');
}
if (req.body.subject && req.body.subject.length > 50) {
errors.push('Subject must be 50 characters or less');
}
if (req.body.email && req.body.email.length > 50) {
errors.push('Email must be 50 characters or less');
}
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread : ''}`
})
}
makePost(req, res, next, numFiles);
});
//report/delete/spoiler/ban
router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, async (req, res, next) => {
const errors = [];
if (!req.body.checkedposts || req.body.checkedposts.length === 0 || req.body.checkedposts.length > 10) {
errors.push('Must select 1-10 posts')
}
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.spoiler
|| req.body.delete
|| req.body.dismiss
|| req.body.global_dismiss
|| req.body.ban
|| req.body.global_ban)) {
errors.push('Invalid actions selected')
}
if (req.body.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}`
})
}
const 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',
'errors': 'Selected posts not found',
'redirect': `/${req.params.board}`
})
}
const messages = [];
try {
// if getting global banned, board ban doesnt matter
if (req.body.global_ban) {
messages.push((await banPoster(req, res, next, null, posts)));
} else if (req.body.ban) {
messages.push((await banPoster(req, res, next, req.params.board, posts)));
}
//ban before deleting
if (req.body.delete) {
messages.push((await deletePosts(req, res, next, posts)));
} else {
// if it was getting deleted, we cant do any of these
if (req.body.spoiler) {
messages.push((await spoilerPosts(req, res, next, posts)));
}
// cannot report and dismiss at same time
if (req.body.report) {
messages.push((await reportPosts(req, res, next)));
} else if (req.body.dismiss) {
messages.push((await dismissReports(req, res, next)));
}
// cannot report and dismiss at same time
if (req.body.global_report) {
messages.push((await globalReportPosts(req, res, next, posts)));
} else if (req.body.global_dismiss) {
messages.push((await dismissGlobalReports(req, res, next, posts)));
}
}
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/${req.params.board}`
});
});
//unban
router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConverter, async (req, res, next) => {
});
//unban
router.post('/board/:board/unban', Boards.exists, banCheck, hasPerms, numberConverter, 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`
});
}
const messages = [];
try {
messages.push((await removeBans(req, res, next)));
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/${req.params.board}/manage`
});
});
router.post('/global/actions', hasPerms, numberConverter, async(req, res, next) => {
const errors = [];
if (!req.body.globalcheckedposts || req.body.globalcheckedposts.length === 0 || req.body.globalcheckedposts.length > 10) {
errors.push('Must select 1-10 posts')
}
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 characters or less');
}
if (!(req.body.spoiler
|| req.body.delete
|| req.body.global_dismiss
|| req.body.global_ban)) {
errors.push('Invalid actions selected')
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/globalmanage'
})
}
const posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true);
if (!posts || posts.length === 0) {
return res.status(404).render('message', {
'title': 'Not found',
'errors': 'Selected posts not found',
'redirect': '/globalmanage'
})
}
const messages = [];
try {
if (req.body.global_ban) {
messages.push((await banPoster(req, res, next, null, posts)));
}
//ban before deleting
if (req.body.delete) {
messages.push((await deletePosts(req, res, next, posts)));
} else {
// if it was getting deleted, we cant do any of these
if (req.body.spoiler) {
messages.push((await spoilerPosts(req, res, next, posts)));
}
if (req.body.global_dismiss) {
messages.push((await dismissGlobalReports(req, res, next, posts)));
}
}
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/${req.params.board}`
});
});
router.post('/global/unban', hasPerms, numberConverter, async(req, res, next) => {
//TODO
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': `/globalmanage`
});
}
const messages = [];
try {
messages.push((await removeBans(req, res, next)));
} catch (err) {
//something not right
if (err.status) {
// return out special error
return res.status(err.status).render('message', err.message);
}
//some other error, use regular error handler
return next(err);
}
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/globalmanage`
});
});
module.exports = router;

@ -32,6 +32,20 @@ module.exports = {
return db.collection('boards').deleteMany({});
},
addBanners: (board, filenames) => {
return db.collection('boards').updateOne(
{
'_id': board,
}, {
'$push': {
'banners': {
'$each': filenames
}
}
}
);
},
cache: async () => {
const boards = await module.exports.find();
for (let i = 0; i < boards.length; i++) {

@ -0,0 +1,15 @@
'use strict';
const path = require('path')
, util = require('util')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js');
module.exports = async (filenames) => {
await Promise.all(filenames.map(async filename => {
unlink(uploadDirectory + filename)
}));
}

@ -1,14 +1,21 @@
'use strict';
const allowedMimeTypes = new Set([
const imageMimeTypes = new Set([
'image/jpeg',
'image/pjpeg',
'image/png',
'image/gif',
]);
const videoMimeTypes = new Set([
'image/webp',
'image/bmp',
'video/mp4',
'video/webm',
]);
module.exports = (mimetype) => allowedMimeTypes.has(mimetype);
module.exports = (mimetype, options) => {
return (options.video && videoMimeTypes.has(mimetype)) || (options.image && imageMimeTypes.has(mimetype));
};

@ -54,7 +54,7 @@ module.exports = async (req, res, next, numFiles) => {
if (numFiles > 0) {
// check all mime types befoer we try saving anything
for (let i = 0; i < numFiles; i++) {
if (!fileCheckMimeType(req.files.file[i].mimetype)) {
if (!fileCheckMimeType(req.files.file[i].mimetype, {image: true, video: true})) {
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.`,
@ -65,6 +65,7 @@ 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];
console.log(file);
const uuid = uuidv4();
const filename = uuid + path.extname(file.name);

@ -0,0 +1,62 @@
'use strict';
const uuidv4 = require('uuid/v4')
, path = require('path')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, fileUpload = require(__dirname+'/../../helpers/files/file-upload.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')
, Boards = require(__dirname+'/../../db/boards.js')
module.exports = async (req, res, next, numFiles) => {
// check if this is responding to an existing thread
let redirect = `/${req.params.board}/manage`
// 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, video: false})) {
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.`,
'redirect': redirect
});
}
}
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);
//add filenames to array add processing to delete previous if one fails
filenames.push(filename);
// try to save
try {
//upload it
await fileUpload(req, res, file, filename);
const imageData = await imageIdentify(filename);
const geometry = imageData.size;
//make sure its 300x100 banner
if (geometry.width !== 300 || geometry.height !== 100) {
await deleteFailedFiles(filenames);
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Invalid file ${file.name}. Banners must be 300x100.`,
'redirect': redirect
});
}
} catch (err) {
//TODO: this better
await deleteFailedFiles(filenames);
return next(err);
}
}
await Boards.addBanners(req.params.board, filenames);
return res.redirect(redirect);
}

@ -17,7 +17,6 @@ module.exports = async (req, res, next) => {
return next(err);
}
//render the page
res.render('board', {
csrf: req.csrfToken(),

@ -55,7 +55,7 @@ const express = require('express')
// use pug view engine
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'views/pages'));
// app.enable('view cache');
app.enable('view cache');
// static files
app.use('/css', express.static(__dirname + '/static/css'));

@ -210,7 +210,7 @@ input, textarea {
.post-files {
float: left;
margin: 0 10px 10px 0;
margin-right: 10px;
}
.post-file {
@ -256,6 +256,10 @@ input textarea {
color: white;
}
.board-banner {
margin: 10px;
}
.board-description {
text-align:center;
margin: 0;
@ -263,7 +267,7 @@ input textarea {
.post-message {
/*overflow-y: auto;*/
float: left;
/*float: left;*/
}
.post-container, .ban {
@ -277,7 +281,7 @@ input textarea {
max-width: 100%;
}
.post-container:target, {
.post-container:target {
background-color: #d6bad0!important;
border-color: #ba9dbf;
}

@ -0,0 +1,14 @@
section.form-wrapper
label.toggle-label Upload Banners
input.toggle(type='checkbox')
form.form-post.togglable(action=`/forms/board/${board._id}/addbanners`, enctype='multipart/form-data', method='POST')
input(type='hidden' name='_csrf' value=csrf)
span
input#file(type='file', name='file' multiple)
input(type='submit', value='submit')

@ -1,4 +1,6 @@
section.board-header
if board.banners
object.board-banner(data=`/img/${board.banners[Math.floor(Math.random()*board.banners.length)]}` width='300' height='100')
a.no-decoration(href=`/${board._id}`)
h1.board-title /#{board._id}/ - #{board.name}
h4.board-description #{board.description}

@ -3,7 +3,6 @@ nav.navbar
a.nav-item.right(href='/logout') Logout
if board
a.nav-item.right(href=`/login?redirect=/${board._id}/`) Login
a.nav-item.right(href=`/${board._id}/manage`) Board Manage
a.nav-item.right(href=`/${board._id}/manage`) Manage
else
a.nav-item.right(href='/login') Login
a.nav-item.right(href='/globalmanage') Global Manage

@ -32,7 +32,7 @@ mixin post(post, truncate, manage=false, globalmanage=false)
if post.spoiler
object(data='/img/spoiler.png' width='128' height='128')
else
object(data=`/img/thumb-${file.filename.split('.')[0]}.png` width='128' height='128')
object(data=`/img/thumb-${file.filename.split('.')[0]}.png` width='128' height='')
if post.message
if truncate
-
@ -51,6 +51,7 @@ mixin post(post, truncate, manage=false, globalmanage=false)
blockquote.post-message !{post.message}
else
blockquote.post-message !{post.message}
if manage === true
each report in post.reports
.reports.post-container

@ -7,6 +7,7 @@ block head
block content
include ../includes/boardheader.pug
include ../includes/bannerform.pug
h4 Reports:
form(action=`/forms/board/${board._id}/actions` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)

@ -22,7 +22,7 @@ const Mongo = require(__dirname+'/db/db.js')
await Posts.deleteAll('b');
console.log('deleting boards')
await Boards.deleteIncrement('pol');
await Boards.deleteIncrement('b');
await Boards.deleteIncrement('b');
await Boards.deleteAll();
await Trips.deleteAll();
console.log('deleting bans');
@ -34,6 +34,7 @@ const Mongo = require(__dirname+'/db/db.js')
description: 'Political posts go here.',
owner: '',
moderators: [],
banners: [],
})
await Boards.insertOne({
_id: 'b',
@ -41,6 +42,7 @@ const Mongo = require(__dirname+'/db/db.js')
description: 'post anything here',
owner: '',
moderators: [],
banners: [],
})
console.log('creating indexes')
await Bans.db.dropIndexes();

Loading…
Cancel
Save