diff --git a/controllers/forms.js b/controllers/forms.js
index 677d87bf..5ebbff9f 100644
--- a/controllers/forms.js
+++ b/controllers/forms.js
@@ -2,95 +2,102 @@
const express = require('express')
, router = express.Router()
- , Boards = require(__dirname+'/../db-models/boards.js')
- , Posts = require(__dirname+'/../db-models/posts.js')
- , Trips = require(__dirname+'/../db-models/trips.js')
+ , 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')
- , numberConverter = require(__dirname+'/../helpers/number-converter.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);
+ 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);
+ 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', Boards.exists, numberConverter, (req, res, next) => {
+router.post('/board/:board/post', Boards.exists, banCheck, numberConverter, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@@ -134,28 +141,38 @@ router.post('/board/:board', Boards.exists, numberConverter, (req, res, next) =>
})
}
- makePost(req, res, numFiles);
+ makePost(req, res, next, numFiles);
});
-//report, delete, sticky, etc
-router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, next) => {
+//report/delete/spoiler/ban
+router.post('/board/:board/actions', Boards.exists, banCheck, numberConverter, async (req, res, next) => {
const errors = [];
- if (!req.body.checked || req.body.checked.length === 0 || req.body.checked.length > 10) {
+ 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.reason && req.body.reason.length > 50) {
+ if (req.body.report_reason && req.body.report_reason.length > 50) {
errors.push('Report must be 50 characters or less');
}
- if (!(req.body.report || req.body.delete || req.body.dismiss || req.body.spoiler)) {
- errors.push('Must select an action')
+ 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.reason || req.body.reason.length === 0)) {
+ if (req.body.report && (!req.body.report_reason || req.body.report_reason.length === 0)) {
errors.push('Reports must have a reason')
}
@@ -167,18 +184,214 @@ router.post('/board/:board/posts', Boards.exists, numberConverter, (req, res, ne
})
}
- if (req.body.report) {
- reportPosts(req, res);
- } else if (req.body.delete) {
- deletePosts(req, res);
- } else if (req.body.spoiler) {
- spoilerPosts(req, res);
- } else if (req.body.dismiss) {
- dismissReports(req, res);
+ 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) => {
+
+ //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;
+
diff --git a/controllers/pages.js b/controllers/pages.js
index c3e3bae0..b863aa89 100644
--- a/controllers/pages.js
+++ b/controllers/pages.js
@@ -2,14 +2,16 @@
const express = require('express')
, router = express.Router()
- , Boards = require(__dirname+'/../db-models/boards.js')
- , checkAuth = require(__dirname+'/../helpers/check-auth.js')
+ , Boards = require(__dirname+'/../db/boards.js')
+ , hasPerms = require(__dirname+'/../helpers/haspermsmiddleware.js')
+ , isLoggedIn = require(__dirname+'/../helpers/isloggedin.js')
, numberConverter = require(__dirname+'/../helpers/number-converter.js')
//page models
- , home = require(__dirname+'/../models/pages/home.js')
- , register = require(__dirname+'/../models/pages/register.js')
- , manage = require(__dirname+'/../models/pages/manage.js')
- , login = require(__dirname+'/../models/pages/login.js')
+ , home = require(__dirname+'/../models/pages/home.js')
+ , register = require(__dirname+'/../models/pages/register.js')
+ , manage = require(__dirname+'/../models/pages/manage.js')
+ , globalmanage = require(__dirname+'/../models/pages/globalmanage.js')
+ , login = require(__dirname+'/../models/pages/login.js')
, board = require(__dirname+'/../models/pages/board.js')
, catalog = require(__dirname+'/../models/pages/catalog.js')
, thread = require(__dirname+'/../models/pages/thread.js');
@@ -24,30 +26,26 @@ router.get('/login', login);
router.get('/register', register);
//logout
-router.get('/logout', (req, res, next) => {
-
- if (req.session.authenticated === true) {
- req.session.destroy();
- return res.render('message', {
- 'title': 'Success',
- 'message': 'You have been logged out successfully',
- 'redirect': '/'
- });
- }
-
- return res.status(400).render('message', {
- 'title': 'Bad request',
- 'message': 'You are not logged in',
- 'redirect': '/login'
- })
+router.get('/logout', isLoggedIn, (req, res, next) => {
+
+ //remove session
+ req.session.destroy();
+ return res.render('message', {
+ 'title': 'Success',
+ 'message': 'You have been logged out successfully',
+ 'redirect': '/'
+ });
});
//board manage page
-router.get('/:board/manage', Boards.exists, checkAuth, Boards.canManage, manage);
+router.get('/:board/manage', Boards.exists, isLoggedIn, hasPerms, manage);
+
+//board manage page
+router.get('/globalmanage', isLoggedIn, hasPerms, globalmanage);
// board page/recents
-router.get('/:board/:page(\\d+)?', Boards.exists, numberConverter, board);
+router.get('/:board', Boards.exists, numberConverter, board);
// thread view page
router.get('/:board/thread/:id(\\d+)', Boards.exists, numberConverter, thread);
diff --git a/db-models/accounts.js b/db/accounts.js
similarity index 95%
rename from db-models/accounts.js
rename to db/accounts.js
index 0d142d8b..9138c218 100644
--- a/db-models/accounts.js
+++ b/db/accounts.js
@@ -1,7 +1,7 @@
'use strict';
-const Mongo = require(__dirname+'/../helpers/db.js')
+const Mongo = require(__dirname+'/db.js')
, db = Mongo.client.db('jschan').collection('accounts')
, bcrypt = require('bcrypt');
diff --git a/db/bans.js b/db/bans.js
new file mode 100644
index 00000000..22fd57b1
--- /dev/null
+++ b/db/bans.js
@@ -0,0 +1,66 @@
+
+'use strict';
+
+const Mongo = require(__dirname+'/db.js')
+ , db = Mongo.client.db('jschan').collection('bans');
+
+module.exports = {
+
+ db,
+
+ find: (ip, board) => {
+ return db.find({
+ 'ip': ip,
+ 'board': {
+ '$in': [board, null]
+ }
+ }).toArray();
+ },
+
+ findMany: (board, ids) => {
+ return db.find({
+ '_id': {
+ '$in': ids
+ },
+ 'board': board
+ }).toArray();
+ },
+
+ getAllBans: () => {
+ return db.find({}).toArray();
+ },
+
+ getGlobalBans: () => {
+ return db.find({
+ 'board': null
+ }).toArray();
+ },
+
+ getBoardBans: (board) => {
+ return db.find({
+ 'board': board,
+ }).toArray();
+ },
+
+ removeMany: (board, ids) => {
+ return db.deleteMany({
+ 'board': board,
+ '_id': {
+ '$in': ids
+ }
+ })
+ },
+
+ insertOne: (ban) => {
+ return db.insertOne(ban);
+ },
+
+ insertMany: (bans) => {
+ return db.insertMany(bans);
+ },
+
+ deleteAll: () => {
+ return db.deleteMany({});
+ },
+
+}
diff --git a/db-models/boards.js b/db/boards.js
similarity index 76%
rename from db-models/boards.js
rename to db/boards.js
index eb5a0f37..cbe09a30 100644
--- a/db-models/boards.js
+++ b/db/boards.js
@@ -1,7 +1,8 @@
'use strict';
-const Mongo = require(__dirname+'/../helpers/db.js')
- , db = Mongo.client.db('jschan');
+const Mongo = require(__dirname+'/db.js')
+ , db = Mongo.client.db('jschan')
+ , boardCache = new Map();
module.exports = {
@@ -11,7 +12,7 @@ module.exports = {
return db.collection('boards').findOne({ '_id': name });
},
- find: (name) => {
+ find: () => {
return db.collection('boards').find({}).toArray();
},
@@ -31,11 +32,20 @@ module.exports = {
return db.collection('boards').deleteMany({});
},
+ cache: async () => {
+ const boards = await module.exports.find();
+ for (let i = 0; i < boards.length; i++) {
+ const board = boards[i];
+ boardCache.set(board._id, board);
+ }
+ },
+
exists: async (req, res, next) => {
- const board = await module.exports.findOne(req.params.board)
+ //const board = await module.exports.findOne(req.params.board);
+ const board = boardCache.get(req.params.board);
if (!board) {
- return res.status(404).render('404')
+ return res.status(404).render('404');
}
res.locals.board = board; // can acces this in views or next route handlers
next();
diff --git a/helpers/db.js b/db/db.js
similarity index 100%
rename from helpers/db.js
rename to db/db.js
diff --git a/db-models/posts.js b/db/posts.js
similarity index 81%
rename from db-models/posts.js
rename to db/posts.js
index 0688a6ce..a8137378 100644
--- a/db-models/posts.js
+++ b/db/posts.js
@@ -1,6 +1,6 @@
'use strict';
-const Mongo = require(__dirname+'/../helpers/db.js')
+const Mongo = require(__dirname+'/db.js')
, Boards = require(__dirname+'/boards.js')
, db = Mongo.client.db('jschan').collection('posts');
@@ -40,7 +40,7 @@ module.exports = {
}
}).sort({
'_id': -1
- }).limit(3).toArray();
+ }).limit(5).toArray();
thread.replies = replies.reverse();
}));
@@ -61,7 +61,8 @@ module.exports = {
const data = await Promise.all([
db.findOne({
'postId': id,
- 'board': board
+ 'board': board,
+ 'thread': null,
}, {
'projection': {
'salt': 0,
@@ -171,6 +172,15 @@ module.exports = {
},
+ //takes array "ids" of mongo ids to get posts from any board
+ globalGetPosts: (ids) => {
+ return db.find({
+ '_id': {
+ '$in': ids
+ },
+ }).toArray();
+ },
+
insertOne: async (board, data) => {
// bump thread if name not sage
@@ -212,6 +222,27 @@ module.exports = {
});
},
+ globalReportMany: (ids, report) => {
+ return db.updateMany({
+ '_id': {
+ '$in': ids
+ },
+ }, {
+ '$push': {
+ 'globalreports': report
+ }
+ });
+ },
+
+ getReports: (board) => {
+ return db.find({
+ 'reports.0': {
+ '$exists': true
+ },
+ 'board': board
+ }).toArray();
+ },
+
dismissReports: (board, ids) => {
return db.updateMany({
'postId': {
@@ -225,50 +256,60 @@ module.exports = {
});
},
- getReports: (board) => {
+ getGlobalReports: () => {
return db.find({
- 'reports.0': {
+ 'globalreports.0': {
'$exists': true
- },
- 'board': board
+ }
}, {
'projection': {
'salt': 0,
'password': 0,
'ip': 0,
+ 'reports': 0,
}
}).toArray();
},
+ dismissGlobalReports: (ids) => {
+ return db.updateMany({
+ '_id': {
+ '$in': ids
+ },
+ }, {
+ '$set': {
+ 'globalreports': []
+ }
+ });
+ },
+
deleteOne: (board, options) => {
return db.deleteOne(options);
},
- deleteMany: (board, ids) => {
+ deleteMany: (ids) => {
return db.deleteMany({
- 'postId': {
+ '_id': {
'$in': ids
- },
- 'board': board
+ }
});
},
- spoilerMany: (board, ids) => {
+ spoilerMany: (ids) => {
- return db.updateMany({
- 'postId': {
- '$in': ids
- },
- 'board': board
- }, {
- '$set': {
+ return db.updateMany({
+ '_id': {
+ '$in': ids
+ }
+ }, {
+ '$set': {
'spoiler': true
}
- });
+ });
- },
+ },
deleteAll: (board) => {
return db.deleteMany({
diff --git a/db-models/trips.js b/db/trips.js
similarity index 85%
rename from db-models/trips.js
rename to db/trips.js
index 22726dbe..c284388e 100644
--- a/db-models/trips.js
+++ b/db/trips.js
@@ -1,6 +1,6 @@
'use strict';
-const Mongo = require(__dirname+'/../helpers/db.js')
+const Mongo = require(__dirname+'/db.js')
, db = Mongo.client.db('jschan').collection('tripcodes');
module.exports = {
diff --git a/helpers/bancheck.js b/helpers/bancheck.js
new file mode 100644
index 00000000..684f4cbf
--- /dev/null
+++ b/helpers/bancheck.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const Bans = require(__dirname+'/../db/bans.js')
+ , hasPerms = require(__dirname+'/hasperms.js');
+
+module.exports = async (req, res, next) => {
+
+ if (!hasPerms(req, res)) {
+ const ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
+ const bans = await Bans.find(ip, res.locals.board ? res.locals.board._id : null);
+ if (bans && bans.length > 0) {
+ //TODO: show posts banned for, expiry, etc
+ return res.status(403).render('ban', {
+ bans: bans
+ });
+ }
+ }
+ next();
+
+}
diff --git a/helpers/check-auth.js b/helpers/check-auth.js
deleted file mode 100644
index 3809e84d..00000000
--- a/helpers/check-auth.js
+++ /dev/null
@@ -1,6 +0,0 @@
-'use strict';
-
-module.exports = (req, res, next) => {
- if (req.session.authenticated === true) return next()
- res.redirect('/login')
-}
diff --git a/helpers/files/video-thumbnail.js b/helpers/files/video-thumbnail.js
index 1633b945..91ad1572 100644
--- a/helpers/files/video-thumbnail.js
+++ b/helpers/files/video-thumbnail.js
@@ -14,7 +14,7 @@ module.exports = (filename) => {
count: 1,
filename: `thumb-${filename.split('.')[0]}.png`,
folder: uploadDirectory,
- size: '128x128'
+ size: '128x?'
});
});
diff --git a/helpers/has-perms.js b/helpers/has-perms.js
deleted file mode 100644
index 3c64c804..00000000
--- a/helpers/has-perms.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-module.exports = (req, res) => {
- return req.session.authenticated //if the user is authed
- && req.session.user //if the user is logged in
- && (req.session.user.authLevel > 1 //and is not a regular user
- || res.locals.board.owner == req.session.user.username //or us board owner
- || res.locals.board.moderators.includes(req.session.user.username)); //or is board moderator
-}
diff --git a/helpers/hasperms.js b/helpers/hasperms.js
new file mode 100644
index 00000000..7131aaac
--- /dev/null
+++ b/helpers/hasperms.js
@@ -0,0 +1,16 @@
+'use strict';
+
+module.exports = (req, res) => {
+ return req.session.authenticated //if the user is authed
+ && req.session.user //if the user is logged in
+ && (
+ req.session.user.authLevel > 1 //and is not a regular user
+ || (
+ res.locals.board
+ && (
+ res.locals.board.owner == req.session.user.username //and board owner
+ || res.locals.board.moderators.includes(req.session.user.username) //or board mod
+ )
+ )
+ )
+}
diff --git a/helpers/haspermsmiddleware.js b/helpers/haspermsmiddleware.js
new file mode 100644
index 00000000..e928c663
--- /dev/null
+++ b/helpers/haspermsmiddleware.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const hasPerms = require(__dirname+'/hasperms.js');
+
+module.exports = async (req, res, next) => {
+
+ if (!hasPerms(req, res)) {
+ return res.status(403).render('message', {
+ 'title': 'Forbidden',
+ 'message': 'You do not have permission to access this page',
+ 'redirect': '/'
+ });
+ }
+ next();
+
+}
diff --git a/helpers/isloggedin.js b/helpers/isloggedin.js
new file mode 100644
index 00000000..69d540c1
--- /dev/null
+++ b/helpers/isloggedin.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = (req, res, next) => {
+ if (req.session.authenticated === true) {
+ return next();
+ }
+ res.redirect('/login');
+}
diff --git a/helpers/markdown.js b/helpers/markdown.js
index a0e100ce..e056568b 100644
--- a/helpers/markdown.js
+++ b/helpers/markdown.js
@@ -1,26 +1,24 @@
'use strict';
-const Posts = require(__dirname+'/../db-models/posts.js')
- , quoteRegex = /^>>\d+$/gm
- , greentextRegex = /^>[^>].+$/gm
- , redtextRegex = /^<[^<].+$/gm
- , boldRegex = /==.+==/gm
- , italicRegex = /__.+__/gm
+const Posts = require(__dirname+'/../db/posts.js')
+ , greentextRegex = /^>([^>].+)/gm
+ , redtextRegex = /^<([^<].+)/gm
+ , boldRegex = /==(.+)==/gm
+ , italicRegex = /__(.+)__/gm
, linkRegex = /https?\:\/\/[^\s]+/g
- , spoilerRegex = /\|.+\|/gm;
+ , spoilerRegex = /\|\|(.+)\|\|/gm
+ , codeRegex = /^```\s([\s\S]+)\s```/gm;
module.exports = (board, thread, text) => {
//redtext
- text = text.replace(redtextRegex, (match) => {
- const red = match.substring(1);
- return `<${red}`;
+ text = text.replace(redtextRegex, (match, redtext) => {
+ return `<${redtext}`;
});
//greentext
- text = text.replace(greentextRegex, (match) => {
- const green = match.substring(1);
- return `>${green}`;
+ text = text.replace(greentextRegex, (match, greentext) => {
+ return `>${greentext}`;
});
//links
@@ -28,30 +26,25 @@ module.exports = (board, thread, text) => {
return `${match}`;
});
- //quotes
- text = text.replace(quoteRegex, (match) => {
- const quotenum = match.substring(2);
- return `>>${quotenum}`;
- });
-
//bold
- text = text.replace(boldRegex, (match) => {
- const bold = match.substring(2, match.length-2);
+ text = text.replace(boldRegex, (match, bold) => {
return `${bold}`;
});
//italic
- text = text.replace(italicRegex, (match) => {
- const italic = match.substring(2, match.length-2);
- return `${italic}`;
+ text = text.replace(italicRegex, (match, italic) => {
+ return `${italic}`;
});
//spoilers
- text = text.replace(spoilerRegex, (match) => {
- const spoiler = match.substring(1, match.length-1);
+ text = text.replace(spoilerRegex, (match, spoiler) => {
return `${spoiler}`;
});
+ text = text.replace(codeRegex, (match, code) => {
+ return `${code.trim()}`;
+ });
+
return text;
}
diff --git a/helpers/number-converter.js b/helpers/number-converter.js
index 149ce2f4..795cf377 100644
--- a/helpers/number-converter.js
+++ b/helpers/number-converter.js
@@ -1,14 +1,19 @@
'use strict';
+const Mongo = require(__dirname+'/../db/db.js');
+
module.exports = (req, res, next) => {
//for body
if (req.body.thread) {
req.body.thread = +req.body.thread;
}
- if (req.body.checked) {
- //syntax casts all string to number
- req.body.checked = req.body.checked.map(Number);
+ if (req.body.checkedposts) {
+ //syntax tries to convert all string to number
+ req.body.checkedposts = req.body.checkedposts.map(Number);
+ }
+ if (req.body.globalcheckedposts) {
+ req.body.globalcheckedposts = req.body.globalcheckedposts.map(Mongo.ObjectId)
}
//and for params
@@ -19,6 +24,16 @@ module.exports = (req, res, next) => {
req.params.page = +req.params.page;
}
+ //and query
+ if (req.query.p) {
+ const pnum = +req.query.p;
+ if (Number.isSafeInteger(pnum)) {
+ req.query.p = +req.query.p;
+ } else {
+ req.query.p = null;
+ }
+ }
+
next();
}
diff --git a/helpers/quotes.js b/helpers/quotes.js
new file mode 100644
index 00000000..428b6cf9
--- /dev/null
+++ b/helpers/quotes.js
@@ -0,0 +1,45 @@
+'use strict';
+
+const Posts = require(__dirname+'/../db/posts.js')
+ , quoteRegex = />>\d+/gm;
+
+module.exports = async (board, text) => {
+
+ //get the matches
+ const matches = text.match(quoteRegex);
+ if (!matches) {
+ return text;
+ }
+
+ //get all the Ids
+ const quoteIds = matches.map(x => +x.substring(2));
+
+ //get all posts with those Ids
+ const posts = await Posts.getPosts(board, quoteIds, false);
+
+ //turn the result into a map of postId => threadId/postId
+ const postThreadObject = {};
+ let validQuotes = 0;
+ for (let i = 0; i < posts.length; i++) {
+ const post = posts[i];
+ postThreadObject[post.postId] = post.thread || post.postId;
+ validQuotes++;
+ }
+
+ //if none of the quotes were real, dont do a replace
+ if (validQuotes === 0) {
+ return text;
+ }
+
+ //then replace the quotes with only ones that exist
+ text = text.replace(quoteRegex, (match) => {
+ const quotenum = +match.substring(2);
+ if (postThreadObject[quotenum]) {
+ return `>>${quotenum}`;
+ }
+ return match;
+ });
+
+ return text;
+
+}
diff --git a/helpers/tripcode.js b/helpers/tripcode.js
index f7736f5c..e1ac247b 100644
--- a/helpers/tripcode.js
+++ b/helpers/tripcode.js
@@ -1,6 +1,6 @@
'use strict';
-const Tripcodes = require(__dirname+'/../db-models/trips.js')
+const Tripcodes = require(__dirname+'/../db/trips.js')
, crypto = require('crypto');
module.exports = async (password) => {
diff --git a/models/api/get-boards.js b/models/api/get-boards.js
deleted file mode 100644
index d8bc0a5d..00000000
--- a/models/api/get-boards.js
+++ /dev/null
@@ -1,17 +0,0 @@
-'use strict';
-
-const Boards = require(__dirname+'/../../db-models/boards.js');
-
-module.exports = async (req, res) => {
- //get a list of boards
- let boards;
- try {
- boards = await Boards.find();
- } catch (err) {
- console.error(err);
- return res.status(500).json({ 'message': 'Error fetching from DB' })
- }
-
- //render the page
- res.json(boards)
-}
diff --git a/models/api/get-catalog.js b/models/api/get-catalog.js
deleted file mode 100644
index 9a4e78e4..00000000
--- a/models/api/get-catalog.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-const Posts = require(__dirname+'/../../db-models/posts.js');
-
-module.exports = async (req, res) => {
- //get the recently bumped thread & preview posts
- let data;
- try {
- data = await Posts.getCatalog(req.params.board);
- } catch (err) {
- console.error(err);
- return res.status(500).json({ 'message': 'Error fetching from DB' });
- }
-
- if (!data) {
- return res.status(404).json({ 'message': 'Not found' });
- }
-
- return res.json(data)
-}
diff --git a/models/api/get-recent.js b/models/api/get-recent.js
deleted file mode 100644
index 2f6cf32b..00000000
--- a/models/api/get-recent.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-const Posts = require(__dirname+'/../../db-models/posts.js');
-
-module.exports = async (req, res) => {
- //get the recently bumped thread & preview posts
- let threads;
- try {
- threads = await Posts.getRecent(req.params.board, req.params.page || 1);
- } catch (err) {
- console.error(err);
- return res.status(500).json({ 'message': 'Error fetching from DB' });
- }
-
- if (!threads || threads.lenth === 0) {
- return res.status(404).json({ 'message': 'Not found' });
- }
-
- return res.json(threads);
-}
diff --git a/models/api/get-thread.js b/models/api/get-thread.js
deleted file mode 100644
index 58fe7e95..00000000
--- a/models/api/get-thread.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict';
-
-const Posts = require(__dirname+'/../../db-models/posts.js');
-
-module.exports = async (req, res) => {
- //get the recently bumped thread & preview posts
- let thread;
- try {
- thread = await Posts.getThread(req.params.board, req.params.id);
- } catch (err) {
- console.error(err);
- return res.status(500).json({ 'message': 'Error fetching from DB' });
- }
-
- if (!thread) {
- return res.status(404).json({ 'message': 'Not found' });
- }
-
- return res.json(thread)
-}
diff --git a/models/forms/ban-poster.js b/models/forms/ban-poster.js
new file mode 100644
index 00000000..25f4218a
--- /dev/null
+++ b/models/forms/ban-poster.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
+ , hasPerms = require(__dirname+'/../../helpers/hasperms.js')
+ , Bans = require(__dirname+'/../../db/bans.js')
+ , Posts = require(__dirname+'/../../db/posts.js');
+
+module.exports = async (req, res, next, board, checkedPosts) => {
+
+ const posts = checkedPosts;
+
+ //if user is not logged in or if logged in but not authed, they cannot ban
+ if (!hasPerms(req, res)) {
+ throw {
+ 'status': 403,
+ 'message': {
+ 'title': 'Forbidden',
+ 'message': 'You do not have permission to issue bans',
+ 'redirect': `/${req.params.board}`
+ }
+ };
+ }
+
+ const bans = posts.map(post => {
+ return {
+ 'ip': post.ip,
+ 'reason': req.body.ban_reason || 'No reason specified',
+ 'board': board,
+ 'post': req.body.preserve_post ? post : null,
+ 'issuer': req.session.user.username,
+ 'date': new Date(),
+ 'expireAt': new Date((new Date).getTime() + (72*1000*60*60)) // 72h ban
+ }
+ });
+
+ const bannedIps = await Bans.insertMany(bans).then(result => result.insertedCount);
+
+ return `Banned ${bannedIps} ips`;
+
+}
diff --git a/models/forms/delete-post.js b/models/forms/delete-post.js
index 20d63536..1087d19e 100644
--- a/models/forms/delete-post.js
+++ b/models/forms/delete-post.js
@@ -5,27 +5,13 @@ const path = require('path')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
- , hasPerms = require(__dirname+'/../../helpers/has-perms.js')
- , Posts = require(__dirname+'/../../db-models/posts.js');
+ , hasPerms = require(__dirname+'/../../helpers/hasperms.js')
+ , Mongo = require(__dirname+'/../../db/db.js')
+ , Posts = require(__dirname+'/../../db/posts.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next, checkedPosts) => {
- //get all posts that were checked
- let posts;
- try {
- posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts
- } catch (err) {
- console.error(err);
- return res.status(500).render('error');
- }
-
- if (!posts || posts.length === 0) {
- return res.status(400).render('message', {
- 'title': 'Bad requests',
- 'message': 'No posts found',
- 'redirect': `/${req.params.board}`
- });
- }
+ let posts = checkedPosts;
//if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null
if (!hasPerms(req, res)) {
@@ -37,20 +23,29 @@ module.exports = async (req, res) => {
&& post.password == req.body.password
});
if (posts.length === 0) {
- return res.status(403).render('message', {
- 'title': 'Forbidden',
- 'message': 'Password did not match any selected posts',
- 'redirect': `/${req.params.board}`
- });
+ throw {
+ 'status': 403,
+ 'message': {
+ 'title': 'Forbidden',
+ 'message': 'Password did not match any selected posts',
+ 'redirect': `/${req.params.board}`
+ }
+ };
}
}
- const threadIds = posts.filter(x => x.thread == null).map(x => x.postId);
+ //filter to threads, then get the board and thread for each
+ const boardThreads = posts.filter(x => x.thread == null).map(x => {
+ return {
+ board: x.board,
+ thread: x.postId
+ };
+ });
//get posts from all threads
let threadPosts = []
- await Promise.all(threadIds.map(async id => {
- const currentThreadPosts = await Posts.getThreadPosts(req.params.board, id);
+ await Promise.all(boardThreads.map(async data => {
+ const currentThreadPosts = await Posts.getThreadPosts(data.board, data.thread);
threadPosts = threadPosts.concat(currentThreadPosts);
return;
}))
@@ -59,14 +54,8 @@ module.exports = async (req, res) => {
const allPosts = posts.concat(threadPosts)
//delete posts from DB
- let deletedPosts = 0;
- try {
- const result = await Posts.deleteMany(req.params.board, allPosts.map(x => x.postId));
- deletedPosts = result.deletedCount;
- } catch (err) {
- console.error(err);
- return res.status(500).render('error');
- }
+ const postMongoIds = allPosts.map(post => Mongo.ObjectId(post._id))
+ const deletedPosts = await Posts.deleteMany(postMongoIds).then(result => result.deletedCount);
//get filenames from all the posts
let fileNames = [];
@@ -79,15 +68,11 @@ module.exports = async (req, res) => {
//dont question it.
return Promise.all([
unlink(uploadDirectory + filename),
- unlink(uploadDirectory + 'thumb-' + filename)
+ unlink(`${uploadDirectory}thumb-${filename.split('.')[0]}.png`)
])
}));
//hooray!
- return res.render('message', {
- 'title': 'Success',
- 'message': `Deleted ${threadIds.length} threads and ${deletedPosts} posts`,
- 'redirect': `/${req.params.board}`
- });
+ return `Deleted ${boardThreads.length} threads and ${deletedPosts-boardThreads.length} posts`
}
diff --git a/models/forms/dismiss-report.js b/models/forms/dismiss-report.js
index c22a663d..4fac5fb0 100644
--- a/models/forms/dismiss-report.js
+++ b/models/forms/dismiss-report.js
@@ -1,31 +1,23 @@
'use strict';
-const Posts = require(__dirname+'/../../db-models/posts.js')
- , hasPerms = require(__dirname+'/../../helpers/has-perms.js');
+const Posts = require(__dirname+'/../../db/posts.js')
+ , hasPerms = require(__dirname+'/../../helpers/hasperms.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next) => {
if (!hasPerms(req, res)) {
- return res.status(403).render('message', {
- 'title': 'Forbidden',
- 'message': `You are not authorised to dismiss reports.`,
- 'redirect': `/${req.params.board}`
- });
+ throw {
+ 'status': 403,
+ 'message': {
+ 'title': 'Forbidden',
+ 'message': `You are not authorised to dismiss reports.`,
+ 'redirect': `/${req.params.board}`
+ }
+ };
}
- try {
- //dismiss reports from all checked posts
- await Posts.dismissReports(req.params.board, req.body.checked);
- } catch (err) {
- console.error(err);
- return res.status(500).render('error');
- }
+ const dismissedReports = await Posts.dismissReports(req.params.board, req.body.checkedposts).then(result => result.modifiedCount);
- //hooray!
- return res.render('message', {
- 'title': 'Success',
- 'message': `Dismissed report(s) successfully`,
- 'redirect': `/${req.params.board}/manage`
- });
+ return `Dismissed ${dismissedReports} reports successfully`;
}
diff --git a/models/forms/dismissglobalreport.js b/models/forms/dismissglobalreport.js
new file mode 100644
index 00000000..3fbb1b2a
--- /dev/null
+++ b/models/forms/dismissglobalreport.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const Mongo = require(__dirname+'/../../db/db.js')
+ , Posts = require(__dirname+'/../../db/posts.js')
+ , hasPerms = require(__dirname+'/../../helpers/hasperms.js');
+
+module.exports = async (req, res, next, posts) => {
+
+ if (!hasPerms(req, res)) {
+ throw {
+ 'status': 403,
+ 'message': {
+ 'title': 'Forbidden',
+ 'message': 'You are not authorised to dismiss global reports.',
+ 'redirect': '/'
+ }
+ };
+ }
+
+ const postMongoIds = posts.map(post => Mongo.ObjectId(post._id))
+ const dismissedCount = await Posts.dismissGlobalReports(postMongoIds).then(result => result.modifiedCount);
+
+ return `Dismissed ${dismissedCount} reports successfully`;
+
+}
diff --git a/models/forms/edit-post.js b/models/forms/edit-post.js
index 6064c249..37efbecd 100644
--- a/models/forms/edit-post.js
+++ b/models/forms/edit-post.js
@@ -1,34 +1,9 @@
'use strict';
-const uuidv4 = require('uuid/v4')
- , path = require('path')
- , Posts = require(__dirname+'/../../db-models/posts.js')
+const Posts = require(__dirname+'/../../db/posts.js');
-module.exports = async (req, res, numFiles) => {
+module.exports = async (req, res, next) => {
- // 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
- });
- }
+ throw new Error('Not implemented');
- // sticky, lock, sage, spoiler, etc
- for (let i = 0; i < req.body.actions.length; i++) {
- //TODO
- }
-
- 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/globalreportpost.js b/models/forms/globalreportpost.js
new file mode 100644
index 00000000..278f312c
--- /dev/null
+++ b/models/forms/globalreportpost.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const Mongo = require(__dirname+'/../../db/db.js')
+ , Posts = require(__dirname+'/../../db/posts.js');
+
+module.exports = async (req, res, next, posts) => {
+
+ const ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
+ const report = {
+ 'reason': req.body.report_reason,
+ 'date': new Date(),
+ 'ip': ip
+ }
+
+ const ids = posts.map(p => Mongo.ObjectId(p._id))
+
+ //push the report to all checked posts
+ const reportedPosts = await Posts.globalReportMany(ids, report).then(result => result.modifiedCount);
+
+ //hooray!
+ return `Global reported ${reportedPosts} posts successfully`
+
+}
diff --git a/models/forms/login.js b/models/forms/login.js
index 07720bef..fa8e8b1a 100644
--- a/models/forms/login.js
+++ b/models/forms/login.js
@@ -1,20 +1,20 @@
'use strict';
const bcrypt = require('bcrypt')
- , Accounts = require(__dirname+'/../../db-models/accounts.js');
+ , Accounts = require(__dirname+'/../../db/accounts.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next) => {
const username = req.body.username.toLowerCase();
const password = req.body.password;
+ const redirect = req.body.redirect;
//fetch an account
let account;
try {
account = await Accounts.findOne(username);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err);
}
//if the account doesnt exist, reject
@@ -22,7 +22,7 @@ module.exports = async (req, res) => {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
- 'redirect': '/login'
+ 'redirect': redirect ? `/login?redirect=${redirect}` : '/login'
});
}
@@ -31,8 +31,7 @@ module.exports = async (req, res) => {
try {
passwordMatch = await bcrypt.compare(password, account.passwordHash);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err);
}
//if hashes matched
@@ -46,18 +45,14 @@ module.exports = async (req, res) => {
req.session.authenticated = true;
//successful login
- return res.render('message', {
- 'title': 'Success',
- 'message': `Welcome, ${username}`,
- 'redirect': '/'
- });
+ return res.redirect(redirect || '/');
}
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
- 'redirect': '/login'
+ 'redirect': redirect ? `/login?redirect=${redirect}` : '/login'
});
}
diff --git a/models/forms/make-post.js b/models/forms/make-post.js
index 65c0e436..30e22da9 100644
--- a/models/forms/make-post.js
+++ b/models/forms/make-post.js
@@ -6,12 +6,13 @@ const uuidv4 = require('uuid/v4')
, crypto = require('crypto')
, randomBytes = util.promisify(crypto.randomBytes)
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
- , Posts = require(__dirname+'/../../db-models/posts.js')
+ , Posts = require(__dirname+'/../../db/posts.js')
, getTripCode = require(__dirname+'/../../helpers/tripcode.js')
+ , linkQuotes = require(__dirname+'/../../helpers/quotes.js')
, simpleMarkdown = require(__dirname+'/../../helpers/markdown.js')
, sanitize = require('sanitize-html')
, sanitizeOptions = {
- allowedTags: [ 'span', 'a', 'code', 'em', 'strong' ],
+ allowedTags: [ 'span', 'a', 'em', 'strong' ],
allowedAttributes: {
'a': [ 'href', 'class' ],
'span': [ 'class' ]
@@ -25,7 +26,7 @@ const uuidv4 = require('uuid/v4')
, videoIdentify = require(__dirname+'/../../helpers/files/video-identify.js')
, formatSize = require(__dirname+'/../../helpers/files/format-size.js')
-module.exports = async (req, res, numFiles) => {
+module.exports = async (req, res, next, numFiles) => {
// check if this is responding to an existing thread
let redirect = `/${req.params.board}`
@@ -35,8 +36,7 @@ module.exports = async (req, res, numFiles) => {
try {
thread = await Posts.getPost(req.params.board, req.body.thread, true);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err);
}
if (!thread || thread.thread != null) {
return res.status(400).render('message', {
@@ -100,7 +100,7 @@ module.exports = async (req, res, numFiles) => {
await videoThumbnail(filename);
break;
default:
- return res.status(500).render('error'); //how did we get here?
+ return next(err);
}
//make thumbnail
@@ -115,13 +115,11 @@ module.exports = async (req, res, numFiles) => {
if (Array.isArray(processedFile.geometryString)) {
processedFile.geometryString = processedFile.geometryString[0];
}
-
files.push(processedFile);
} catch (err) {
- console.error(err);
//TODO: DELETE FAILED FILES
- return res.status(500).render('error');
+ return next(err);
}
}
}
@@ -149,7 +147,9 @@ module.exports = async (req, res, numFiles) => {
//simple markdown and sanitize
let message = req.body.message;
if (message && message.length > 0) {
- message = sanitize(simpleMarkdown(req.params.board, req.body.thread, message), sanitizeOptions);
+ message = simpleMarkdown(req.params.board, req.body.thread, message);
+ message = await linkQuotes(req.params.board, message);
+ message = sanitize(message, sanitizeOptions);
}
//add post to DB
@@ -167,6 +167,7 @@ module.exports = async (req, res, numFiles) => {
'files': files,
'salt': !req.body.thread ? salt : '',
'reports': [],
+ 'globalreports': [],
'spoiler': req.body.spoiler ? true : false,
};
@@ -174,11 +175,10 @@ module.exports = async (req, res, numFiles) => {
try {
postId = await Posts.insertOne(req.params.board, data);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err);
}
- const successRedirect = `/${req.params.board}/thread/${req.body.thread || postId}`;
+ const successRedirect = `/${req.params.board}/thread/${req.body.thread || postId}#${postId}`;
return res.redirect(successRedirect);
}
diff --git a/models/forms/register.js b/models/forms/register.js
index 023357e3..51f96bf5 100644
--- a/models/forms/register.js
+++ b/models/forms/register.js
@@ -1,9 +1,9 @@
'use strict';
const bcrypt = require('bcrypt')
- , Accounts = require(__dirname+'/../../db-models/accounts.js');
+ , Accounts = require(__dirname+'/../../db/accounts.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next) => {
const username = req.body.username.toLowerCase();
const password = req.body.password;
@@ -12,8 +12,7 @@ module.exports = async (req, res) => {
try {
account = await Accounts.findOne(username);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err);
}
// if the account exists reject
@@ -29,14 +28,9 @@ module.exports = async (req, res) => {
try {
await Accounts.insertOne(username, password, 1);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err);
}
- return res.render('message', {
- 'title': 'Success',
- 'message': `Welcome, ${username}`,
- 'redirect': '/'
- });
+ return res.redirect('/login')
}
diff --git a/models/forms/removebans.js b/models/forms/removebans.js
new file mode 100644
index 00000000..db0af77a
--- /dev/null
+++ b/models/forms/removebans.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const Bans = require(__dirname+'/../../db/bans.js')
+ , { ObjectId } = require('mongodb');
+
+module.exports = async (req, res, next) => {
+
+ const banIds = req.body.checkedbans.map(ObjectId);
+ const removedBans = await Bans.removeMany(req.params.board, banIds).then(result => result.deletedCount);
+
+ return `Removed ${removedBans} bans`;
+
+}
diff --git a/models/forms/report-post.js b/models/forms/report-post.js
index 4f454a93..49bb7efb 100644
--- a/models/forms/report-post.js
+++ b/models/forms/report-post.js
@@ -1,29 +1,20 @@
'use strict';
-const Posts = require(__dirname+'/../../db-models/posts.js');
+const Posts = require(__dirname+'/../../db/posts.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next) => {
const ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
const report = {
- 'reason': req.body.reason,
+ 'reason': req.body.report_reason,
'date': new Date(),
'ip': ip
}
- try {
- //push the report to all checked posts
- await Posts.reportMany(req.params.board, req.body.checked, report);
- } catch (err) {
- console.error(err);
- return res.status(500).render('error');
- }
+ //push the report to all checked posts
+ const reportedCount = await Posts.reportMany(req.params.board, req.body.checkedposts, report).then(result => result.modifiedCount);
//hooray!
- return res.render('message', {
- 'title': 'Success',
- 'message': `Reported post(s) successfully`,
- 'redirect': `/${req.params.board}`
- });
+ return `Reported ${reportedCount} posts successfully`
}
diff --git a/models/forms/spoiler-post.js b/models/forms/spoiler-post.js
index 15847f77..6dcabb93 100644
--- a/models/forms/spoiler-post.js
+++ b/models/forms/spoiler-post.js
@@ -5,27 +5,14 @@ const path = require('path')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
- , hasPerms = require(__dirname+'/../../helpers/has-perms.js')
- , Posts = require(__dirname+'/../../db-models/posts.js');
+ , hasPerms = require(__dirname+'/../../helpers/hasperms.js')
+ , Mongo = require(__dirname+'/../../db/db.js')
+ , Posts = require(__dirname+'/../../db/posts.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next, checkedPosts) => {
//get all posts that were checked
- let posts;
- try {
- posts = await Posts.getPosts(req.params.board, req.body.checked, true); //admin arument true, fetches passwords and salts
- } catch (err) {
- console.error(err);
- return res.status(500).render('error');
- }
-
- if (!posts || posts.length === 0) {
- return res.status(400).render('message', {
- 'title': 'Bad requests',
- 'message': 'No posts found',
- 'redirect': `/${req.params.board}`
- });
- }
+ let posts = checkedPosts;
//if user is not logged in OR if lgoged in but not authed, filter the posts by passwords that are not null
if (!hasPerms(req, res)) {
@@ -37,42 +24,38 @@ module.exports = async (req, res) => {
&& post.password == req.body.password
});
if (posts.length === 0) {
- return res.status(403).render('message', {
- 'title': 'Forbidden',
- 'message': 'Password did not match any selected posts',
- 'redirect': `/${req.params.board}`
- });
+ throw {
+ 'status': 403,
+ 'message': {
+ 'title': 'Forbidden',
+ 'message': 'Password did not match any selected posts',
+ 'redirect': '/'
+ }
+ };
}
}
- //filter by not spoilered
+ //filter by not spoilered. maybe i add filters with optiins in the controller where it gets the posts?
posts = posts.filter(post => {
return !post.spoiler
});
if (posts.length === 0) {
- return res.status(409).render('message', {
- 'title': 'Conflict',
- 'message': 'Selected posts are already spoilered',
- 'redirect': `/${req.params.board}`
- });
+ throw {
+ 'status': 409,
+ 'message': {
+ 'title': 'Conflict',
+ 'message': 'Posts already spoilered',
+ 'redirect': '/'
+ }
+ };
}
// spoiler posts
- let spoileredPosts = 0;
- try {
- const result = await Posts.spoilerMany(req.params.board, posts.map(x => x.postId));
- spoileredPosts = result.modifiedCount;
- } catch (err) {
- console.error(err);
- return res.status(500).render('error');
- }
+ const postMongoIds = posts.map(post => Mongo.ObjectId(post._id));
+ const spoileredPosts = await Posts.spoilerMany(postMongoIds).then(result => result.modifiedCount);
//hooray!
- return res.render('message', {
- 'title': 'Success',
- 'message': `Spoilered ${spoileredPosts} posts`,
- 'redirect': `/${req.params.board}`
- });
+ return `Spoilered ${spoileredPosts} posts`
}
diff --git a/models/pages/board.js b/models/pages/board.js
index a4f4a64b..649ebae0 100644
--- a/models/pages/board.js
+++ b/models/pages/board.js
@@ -1,23 +1,28 @@
'use strict';
-const Posts = require(__dirname+'/../../db-models/posts.js');
+const Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next) => {
//get the recently bumped thread & preview posts
+ const page = req.query.p || 1;
let threads;
let pages;
try {
- threads = await Posts.getRecent(req.params.board, req.params.page || 1);
- pages = Math.ceil((await Posts.getPages(req.params.board)) / 10);
+ pages = Math.ceil((await Posts.getPages(req.params.board)) / 10) || 1;
+ if (page > pages) {
+ return next();
+ }
+ threads = await Posts.getRecent(req.params.board, page);
} catch (err) {
- console.error(err)
- return next();
+ return next(err);
}
+
//render the page
res.render('board', {
csrf: req.csrfToken(),
threads: threads || [],
- pages: pages
+ pages,
+ page,
});
}
diff --git a/models/pages/catalog.js b/models/pages/catalog.js
index aefd36ce..2dd42f0a 100644
--- a/models/pages/catalog.js
+++ b/models/pages/catalog.js
@@ -1,6 +1,6 @@
'use strict';
-const Posts = require(__dirname+'/../../db-models/posts.js');
+const Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next) => {
@@ -9,8 +9,7 @@ module.exports = async (req, res, next) => {
try {
threads = await Posts.getCatalog(req.params.board);
} catch (err) {
- console.error(err);
- return next();
+ return next(err);
}
//render the page
diff --git a/models/pages/globalmanage.js b/models/pages/globalmanage.js
new file mode 100644
index 00000000..f0dfa034
--- /dev/null
+++ b/models/pages/globalmanage.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const Posts = require(__dirname+'/../../db/posts.js')
+ , Bans = require(__dirname+'/../../db/bans.js');
+
+module.exports = async (req, res, next) => {
+
+ let reports;
+ let bans;
+ try {
+ reports = await Posts.getGlobalReports();
+ bans = await Bans.getGlobalBans();
+ } catch (err) {
+ return next(err)
+ }
+
+ //render the page
+ res.render('globalmanage', {
+ csrf: req.csrfToken(),
+ reports,
+ bans,
+ });
+
+}
diff --git a/models/pages/home.js b/models/pages/home.js
index f8640e0f..cdddc7ba 100644
--- a/models/pages/home.js
+++ b/models/pages/home.js
@@ -1,6 +1,6 @@
'use strict';
-const Boards = require(__dirname+'/../../db-models/boards.js');
+const Boards = require(__dirname+'/../../db/boards.js');
module.exports = async (req, res, next) => {
@@ -9,8 +9,7 @@ module.exports = async (req, res, next) => {
try {
boards = await Boards.find();
} catch (err) {
- console.error(err)
- return next();
+ return next(err);
}
//render the page
diff --git a/models/pages/login.js b/models/pages/login.js
index d4241372..373b330d 100644
--- a/models/pages/login.js
+++ b/models/pages/login.js
@@ -1,10 +1,11 @@
'use strict';
-module.exports = (req, res) => {
+module.exports = (req, res, next) => {
//render the page
res.render('login', {
- csrf: req.csrfToken()
+ csrf: req.csrfToken(),
+ redirect: req.query.redirect,
});
}
diff --git a/models/pages/manage.js b/models/pages/manage.js
index 4a1de412..962f5039 100644
--- a/models/pages/manage.js
+++ b/models/pages/manage.js
@@ -1,21 +1,24 @@
'use strict';
-const Posts = require(__dirname+'/../../db-models/posts.js');
+const Posts = require(__dirname+'/../../db/posts.js')
+ , Bans = require(__dirname+'/../../db/bans.js');
-module.exports = async (req, res) => {
+module.exports = async (req, res, next) => {
- let posts;
+ let reports;
+ let bans;
try {
- posts = await Posts.getReports(req.params.board);
+ reports = await Posts.getReports(req.params.board);
+ bans = await Bans.getBoardBans(req.params.board);
} catch (err) {
- console.error(err);
- return res.status(500).render('error');
+ return next(err)
}
//render the page
res.render('manage', {
csrf: req.csrfToken(),
- posts: posts
+ reports,
+ bans,
});
}
diff --git a/models/pages/register.js b/models/pages/register.js
index ce522342..f8ce78fb 100644
--- a/models/pages/register.js
+++ b/models/pages/register.js
@@ -1,15 +1,6 @@
'use strict';
-module.exports = (req, res) => {
-
- //send home if already logged in
- if (req.session.authenticated === true) {
- return res.status(400).render('message', {
- 'title': 'Notice',
- 'message': 'You are already logged in. Redirecting you to back home.',
- 'redirect': '/'
- });
- }
+module.exports = (req, res, next) => {
//render the page
res.render('register', {
diff --git a/models/pages/thread.js b/models/pages/thread.js
index 67b2b75d..1a887e60 100644
--- a/models/pages/thread.js
+++ b/models/pages/thread.js
@@ -1,6 +1,6 @@
'use strict';
-const Posts = require(__dirname+'/../../db-models/posts.js');
+const Posts = require(__dirname+'/../../db/posts.js');
module.exports = async (req, res, next) => {
@@ -9,8 +9,7 @@ module.exports = async (req, res, next) => {
try {
thread = await Posts.getThread(req.params.board, req.params.id);
} catch (err) {
- console.error(err);
- return next();
+ return next(err);
}
if (!thread) {
diff --git a/server.js b/server.js
index 5ee75cd9..a91533fc 100644
--- a/server.js
+++ b/server.js
@@ -13,13 +13,15 @@ const express = require('express')
, bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, configs = require(__dirname+'/configs/main.json')
- , Mongo = require(__dirname+'/helpers/db.js')
+ , Mongo = require(__dirname+'/db/db.js')
, upload = require('express-fileupload');
(async () => {
// let db connect
await Mongo.connect();
+ const Boards = require(__dirname+'/db/boards.js');
+ await Boards.cache();
// parse forms and allow file uploads
app.use(bodyParser.urlencoded({extended: true}));
@@ -53,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'));
@@ -74,7 +76,10 @@ const express = require('express')
return res.status(403).send('Invalid CSRF token')
}
console.error(err.stack)
- return res.status(500).render('error')
+ return res.status(500).render('message', {
+ 'title': 'Internal Server Error',
+ 'redirect': req.header('Referer') || '/'
+ })
})
// listen
diff --git a/static/css/style.css b/static/css/style.css
index 5a6d4c3d..04af046d 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -9,10 +9,28 @@ body {
margin: 0;
}
+.code {
+ border-left: 10px solid #B7C5D9;
+ display: block;
+ padding-left: 5px;
+ font-family: monospace;
+ margin: 0.5em 0;
+}
+
+.pages a {
+ text-decoration: none;
+}
+
object {
object-fit: scale-down;
}
+.board-header {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
.catalog-tile-button {
width: 100%;
line-height: 30px;
@@ -46,13 +64,14 @@ object {
box-shadow: 0 0 3px black;
min-width: 64px;
min-height: 64px;
+ object-fit: cover;
}
.catalog {
display:flex;
- flex-direction: row;
align-items:flex-start;
- flex-wrap: wrap;
+ justify-content: space-evenly;
+ flex-flow: row wrap;
}
.spoiler {
@@ -85,6 +104,10 @@ object {
color: green;
}
+blockquote a {
+ color: #d00;
+}
+
blockquote {
word-break: break-all;
white-space: pre-wrap;
@@ -115,15 +138,62 @@ input, textarea {
margin: 10px 0;
}
+.actions {
+ background: #D6DAF0;
+ border-color: #B7C5D9;
+ border-width: 0 1px 1px 0;
+ border-style: none solid solid none;
+ max-width: 100%;
+ display: flex;
+ flex-direction: column;
+ margin: 2px 0;
+ padding: 2px;
+}
+
.action-wrapper {
- align-items: center;
- /*flex-direction: row;*/
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.actions label {
+ margin: 2px 0;
+}
+
+.toggle-label:hover {
+ box-shadow: inset 0 0 100px 100px rgba(255,255,255,.25);
+}
+
+.toggle-label {
+ background: #D6DAF0;
+ padding: 10px;
+ border-radius: 3px;
+ text-align: center;
+ border: 1px solid lightgray;
+ max-width: 100%;
+ box-sizing: border-box;
+ /*-webkit-appearance: button;
+ -moz-appearance: button;
+ appearance: button;*/
+}
+
+.toggle {
+ display: none;
+}
+
+.toggle:checked + .form-post {
+ display: flex;
}
.form-post {
display: flex;
flex-direction: column;
max-width: 100%;
+ margin-top: 10px;
+}
+
+.togglable {
+ display: none;
}
.user-id {
@@ -135,9 +205,7 @@ input, textarea {
.post-check {
position: relative;
top: 3px;
- margin: 2px;
- margin-right: 4px;
- padding: 0;
+ margin: -3px 1px !important;
}
.post-files {
@@ -177,7 +245,7 @@ input textarea {
.board-title {
color: #af0a0f;
- font: bolder;
+ font: bolder 28px Tahoma;
letter-spacing: -2px;
text-align: center;
margin: 0;
@@ -193,8 +261,13 @@ input textarea {
margin: 0;
}
-.post-container {
- margin: 1px;
+.post-message {
+ overflow-y: auto;
+}
+
+.post-container, .ban {
+ box-sizing: border-box;
+ margin: 2px 0;
padding: 2px;
background: #D6DAF0;
border-color: #B7C5D9;
@@ -204,7 +277,7 @@ input textarea {
}
.post-container:target, .op:target {
- background-color: #d6bad0;
+ background-color: #d6bad0!important;
border-color: #ba9dbf;
}
@@ -213,7 +286,6 @@ input textarea {
margin-left: 0;
display: block;
border: none;
- width: 100%;
}
.post-subject {
@@ -226,16 +298,22 @@ input textarea {
font-weight: bold;
}
-.post-info {
- margin-top: -2px;
+.post-container.op .post-info, .catalog-tile-content .post-info {
+ background: none;
}
-.post-image {
+.post-info {
+ margin: -2px;
+ /*margin-left: -3px;*/
+ padding: 2px;
+ padding-left: 1px;
+ /*background-color: #B7C5D9;*/
}
-.post-content {
-
+.post-info * {
+ margin-bottom: 0;
+ margin-top: -2px;
}
.navbar {
@@ -244,6 +322,7 @@ input textarea {
position: fixed;
width: 100%;
background: #eef2ff;
+ z-index: 1;
}
.nav-item {
@@ -256,8 +335,12 @@ input textarea {
border-right: 1px solid lightgray;
}
+.right {
+ float: right;
+}
+
.nav-item:hover {
- box-shadow: inset 0 0 100px 100px rgba(255,255,255,.1);
+ box-shadow: inset 0 0 100px 100px rgba(255,255,255,.25);
}
.footer {
@@ -274,7 +357,6 @@ table, th, td {
}
.boards-table {
- font-size: 16pt;
margin: 0 auto;
}
@@ -306,8 +388,13 @@ hr {
width: 100%;
}
+ blockquote {
+ margin: 1em;
+ }
+
.post-check {
top: 2px;
+ margin-left: 2px!important;
height: 8px;
}
@@ -317,8 +404,14 @@ hr {
.catalog-tile {
overflow-y: hidden;
- width: 49%;
- justify-content: space-evenly;
+ }
+
+ .boards-table {
+ width: 100%;
+ }
+
+ .post-info {
+ background-color: #B7C5D9;
}
}
diff --git a/views/includes/actionfooter.pug b/views/includes/actionfooter.pug
new file mode 100644
index 00000000..ec26a9b0
--- /dev/null
+++ b/views/includes/actionfooter.pug
@@ -0,0 +1,31 @@
+.action-wrapper
+ .actions Actions:
+ label
+ input.post-check(type='checkbox', name='delete' value=1)
+ | Delete
+ label
+ input.post-check(type='checkbox', name='spoiler' value=1)
+ | Spoiler Images
+ label
+ input#password(type='text', name='password', placeholder='post password' autocomplete='off')
+ label
+ input.post-check(type='checkbox', name='report' value=1)
+ | Report
+ label
+ input.post-check(type='checkbox', name='global_report' value=1)
+ | Global Report
+ label
+ input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
+ .actions Mod Actions:
+ label
+ input.post-check(type='checkbox', name='ban' value=1)
+ | Ban Poster
+ label
+ input.post-check(type='checkbox', name='global_ban' value=1)
+ | Global Ban Poster
+ label
+ input.post-check(type='checkbox', name='preserve_post' value=1)
+ | Show Post In Ban
+ label
+ input#report(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
+ input(type='submit', value='submit')
diff --git a/views/includes/actionfooter_globalmanage.pug b/views/includes/actionfooter_globalmanage.pug
new file mode 100644
index 00000000..1d754996
--- /dev/null
+++ b/views/includes/actionfooter_globalmanage.pug
@@ -0,0 +1,24 @@
+.action-wrapper
+ .actions Actions:
+ label
+ input.post-check(type='checkbox', name='delete' value=1)
+ | Delete
+ label
+ input.post-check(type='checkbox', name='spoiler' value=1)
+ | Spoiler Images
+ label
+ input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
+ .actions Mod Actions:
+ label
+ input.post-check(type='checkbox', name='global_dismiss' value=1)
+ | Dismiss Global Reports
+ label
+ input.post-check(type='checkbox', name='global_ban' value=1)
+ | Global Ban Poster
+ label
+ input.post-check(type='checkbox', name='preserve_post' value=1)
+ | Show Post In Ban
+ label
+ input#report(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
+ input(type='submit', value='submit')
+
diff --git a/views/includes/actionfooter_manage.pug b/views/includes/actionfooter_manage.pug
new file mode 100644
index 00000000..7cbe4f2d
--- /dev/null
+++ b/views/includes/actionfooter_manage.pug
@@ -0,0 +1,32 @@
+.action-wrapper
+ .actions Actions:
+ label
+ input.post-check(type='checkbox', name='delete' value=1)
+ | Delete
+ label
+ input.post-check(type='checkbox', name='spoiler' value=1)
+ | Spoiler Images
+ label
+ input#password(type='text', name='password', placeholder='post password' autocomplete='off')
+ label
+ input.post-check(type='checkbox', name='global_report' value=1)
+ | Global Report
+ label
+ input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
+ .actions Mod Actions:
+ label
+ input.post-check(type='checkbox', name='dismiss' value=1)
+ | Dismiss Reports
+ label
+ input.post-check(type='checkbox', name='ban' value=1)
+ | Ban Poster
+ label
+ input.post-check(type='checkbox', name='global_ban' value=1)
+ | Global Ban Poster
+ label
+ input.post-check(type='checkbox', name='preserve_post' value=1)
+ | Show Post In Ban
+ label
+ input#report(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
+ input(type='submit', value='submit')
+
diff --git a/views/includes/boardheader.pug b/views/includes/boardheader.pug
index b95ca162..265ef00e 100644
--- a/views/includes/boardheader.pug
+++ b/views/includes/boardheader.pug
@@ -1,3 +1,4 @@
-a.no-decoration(href=`/${board._id}`)
- h1.board-title /#{board._id}/ - #{board.name}
-h4.board-description #{board.description}
+section.board-header
+ a.no-decoration(href=`/${board._id}`)
+ h1.board-title /#{board._id}/ - #{board.name}
+ h4.board-description #{board.description}
diff --git a/views/includes/deletefooter.pug b/views/includes/deletefooter.pug
deleted file mode 100644
index 7f74c19f..00000000
--- a/views/includes/deletefooter.pug
+++ /dev/null
@@ -1,15 +0,0 @@
-section.action-wrapper
- span
- label
- input.post-check(type='checkbox', name='delete' value=1)
- | Delete
- label
- input.post-check(type='checkbox', name='spoiler' value=1)
- | Spoiler
- input#password(type='password', name='password', placeholder='post password' autocomplete='off')
- span
- label
- input.post-check(type='checkbox', name='report' value=1)
- | Report
- input#report(type='text', name='reason', placeholder='reason' autocomplete='off')
- input(type='submit', value='submit')
diff --git a/views/includes/navbar.pug b/views/includes/navbar.pug
index 9d77b7a0..ad2623ca 100644
--- a/views/includes/navbar.pug
+++ b/views/includes/navbar.pug
@@ -1,5 +1,9 @@
nav.navbar
a.nav-item(href='/') Home
- a.nav-item(href='/login') Login
if board
- a.nav-item(href=`/${board._id}/manage`) Manage Board
+ a.nav-item.right(href=`/${board._id}/manage`) Manage Board
+ a.nav-item.right(href=`/login?redirect=/${board._id}/`) Login
+ else
+ a.nav-item.right(href='/login') Login
+ a.nav-item.right(href='/register') Register
+ a.nav-item.right(href='/logout') Logout
diff --git a/views/includes/pages.pug b/views/includes/pages.pug
new file mode 100644
index 00000000..e9834882
--- /dev/null
+++ b/views/includes/pages.pug
@@ -0,0 +1,6 @@
+span.pages Page:
+ - for(let i = 1; i <= pages; i++)
+ if i === page
+ span: a(href=`/${board._id}?p=${i}`) [#{i}]
+ else
+ span: a(href=`/${board._id}?p=${i}`) #{i}
diff --git a/views/includes/postform.pug b/views/includes/postform.pug
index d304b6a1..7277af5e 100644
--- a/views/includes/postform.pug
+++ b/views/includes/postform.pug
@@ -1,25 +1,29 @@
section.form-wrapper
- form.form-post(action='/forms/board/'+board._id, enctype='multipart/form-data', method='POST')
+ label.toggle-label Show Post Form
+ input.toggle(type='checkbox')
+ form.form-post.togglable(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)
+ input(type='hidden' name='_csrf' value=csrf)
- input#title(type='text', name='subject', placeholder='subject' autocomplete='off' maxlength='50')
+ input(type='hidden' name='thread' value=thread != null ? thread.postId : null)
- input#name(type='text', name='name', placeholder='name' autocomplete='off' maxlength='50')
-
- input#name(type='text', name='email', placeholder='email' autocomplete='off' maxlength='50')
+ input#title(type='text', name='subject', placeholder='subject' autocomplete='off' maxlength='50')
- input#password(type='password', name='password', placeholder='post password' autocomplete='off' maxlength='50')
+ input#name(type='text', name='name', placeholder='name' autocomplete='off' maxlength='50')
- textarea#message(name='message', rows='8', cols='50', placeholder='message' autocomplete='off' maxlength='2000')
+ input#name(type='text', name='email', placeholder='email' autocomplete='off' maxlength='50')
- span
- input#file(type='file', name='file' multiple)
- label
- input.post-check#spoiler(type='checkbox', name='spoiler', value='true')
- | Spoiler
-
- input(type='submit', value='submit')
+ input#password(type='password', name='password', placeholder='post password' autocomplete='off' maxlength='50')
+
+ textarea#message(name='message', rows='8', cols='50', placeholder='message' autocomplete='off' maxlength='2000')
+ span
+ input#file(type='file', name='file' multiple)
+ label
+ input.post-check#spoiler(type='checkbox', name='spoiler', value='true')
+ | Spoiler
+
+ input(type='submit', value='submit')
+
+
+
diff --git a/views/mixins/ban.pug b/views/mixins/ban.pug
new file mode 100644
index 00000000..c6043836
--- /dev/null
+++ b/views/mixins/ban.pug
@@ -0,0 +1,18 @@
+include ./post.pug
+
+mixin ban(ban)
+ .ban
+ input.post-check(type='checkbox', name='checkedbans[]' value=ban._id)
+ if ban.board
+ div Board: #[a(href=`/${ban.board}`) /#{ban.board}/]
+ else
+ div Global ban.
+ div Reason: #{ban.reason}
+ div Issuer: #{ban.issuer}
+ div Date: #{ban.date}
+ div Expiry: #{ban.expireAt}
+ if ban.post
+ span Post:
+ section.thread
+ +post(ban.post, false)
+
diff --git a/views/mixins/catalogtile.pug b/views/mixins/catalogtile.pug
index 462db87b..1fc4588f 100644
--- a/views/mixins/catalogtile.pug
+++ b/views/mixins/catalogtile.pug
@@ -4,7 +4,10 @@ mixin catalogtile(board, post, truncate)
if post.files.length > 0
.post-file-src
a(href=`/${board._id}/thread/${post.postId}#${post.postId}`)
- object.catalog-thumb(data=`/img/thumb-${post.files[0].filename.split('.')[0]}.png` width='64' height='64')
+ 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]}.png` width='64' height='64')
header.post-info
if post.subject
span: a.no-decoration.post-subject(href=`/${board._id}/thread/${post.postId}#${post.postId}`) #{post.subject}
diff --git a/views/mixins/post.pug b/views/mixins/post.pug
index 9af10305..a4d31ba8 100644
--- a/views/mixins/post.pug
+++ b/views/mixins/post.pug
@@ -1,7 +1,11 @@
-mixin post(board, post, truncate)
+mixin post(post, truncate, manage, globalmanage)
article(id=post.postId class='post-container '+(post.thread ? '' : 'op'))
header.post-info
- input.post-check(type='checkbox', name='checked[]' value=post.postId)
+ span
+ if globalmanage
+ input.post-check(type='checkbox', name='globalcheckedposts[]' value=post._id)
+ else
+ input.post-check(type='checkbox', name='checkedposts[]' value=post.postId)
if post.subject
span.post-subject #{post.subject}
if post.email
@@ -11,12 +15,12 @@ mixin post(board, post, truncate)
span.post-name #{post.name}
span #{post.date.toLocaleString()}
span.user-id(style=`background: #${post.userId}`) #{post.userId}
- span: a(href=`/${board._id}/thread/${post.thread ? post.thread : post.postId}#${post.postId}`) ##{post.postId}
+ span: a(href=`/${post.board}/thread/${post.thread || post.postId}#${post.postId}`) ##{post.postId}
if post.files.length > 0
.post-files
each file in post.files
.post-file
- .post-file-info
+ small.post-file-info
span: a(href='/img/'+file.filename download=file.originalFilename) #{file.originalFilename}
br
span (#{file.sizeString} #{file.geometryString})
@@ -39,13 +43,18 @@ mixin post(board, post, truncate)
}
if truncated
blockquote.post-message !{truncatedMessage}
- p Message too long. #[a(href=`/${board._id}/thread/${post.thread == null ? post.postId : post.thread}#${post.postId}`) Click here] to view the full text.
+ p Message too long. #[a(href=`/${post.board}/thread/${post.thread || post.postId}#${post.postId}`) Click here] to view the full text.
else
blockquote.post-message !{post.message}
else
blockquote.post-message !{post.message}
- if post.reports
+ if manage === true
each report in post.reports
.reports.post-container
span Date: #{report.date.toLocaleString()}
span Reason: #{report.reason}
+ if globalmanage === true
+ each report in post.globalreports
+ .reports.post-container
+ span Date: #{report.date.toLocaleString()}
+ span Reason: #{report.reason}
diff --git a/views/pages/ban.pug b/views/pages/ban.pug
new file mode 100644
index 00000000..1a733185
--- /dev/null
+++ b/views/pages/ban.pug
@@ -0,0 +1,14 @@
+extends ../layout.pug
+include ../mixins/ban.pug
+
+block head
+ title Banned!
+
+block content
+ h1.board-title Banned!
+ hr(size=1)
+ Bans currently in place against your IP:
+ hr(size=1)
+ for ban in bans
+ +ban(ban)
+ hr(size=1)
diff --git a/views/pages/board.pug b/views/pages/board.pug
index 75137981..daac336b 100644
--- a/views/pages/board.pug
+++ b/views/pages/board.pug
@@ -6,24 +6,22 @@ block head
block content
include ../includes/boardheader.pug
- hr(size=1)
include ../includes/postform.pug
.mode Posting mode: Thread [#[a.no-decoration(href=`/${board._id}/catalog`) Catalog]]
hr(size=1)
- form(action='/forms/board/'+board._id+'/posts' method='POST' enctype='application/x-www-form-urlencoded')
+ include ../includes/pages.pug
+ 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)
if threads.length === 0
p No posts.
hr(size=1)
for thread in threads
section.thread
- +post(board, thread, true)
+ +post(thread, true)
for post in thread.replies
- +post(board, post, true)
- hr(size=1)
- if pages > 0
- span.pages Page
- - for(let i = 0; i < pages; i++)
- span: a(href=`/${board._id}/${i+1}`) #{i+1}
+ +post(post, true)
hr(size=1)
- include ../includes/deletefooter.pug
+ include ../includes/pages.pug
+ hr(size=1)
+ include ../includes/actionfooter.pug
diff --git a/views/pages/globalmanage.pug b/views/pages/globalmanage.pug
new file mode 100644
index 00000000..faedc003
--- /dev/null
+++ b/views/pages/globalmanage.pug
@@ -0,0 +1,35 @@
+extends ../layout.pug
+include ../mixins/post.pug
+include ../mixins/ban.pug
+
+block head
+ title Manage
+
+block content
+ h1.board-title Global Management
+ h4 All Reports:
+ form(action=`/forms/global/actions` method='POST' enctype='application/x-www-form-urlencoded')
+ input(type='hidden' name='_csrf' value=csrf)
+ if reports.length === 0
+ p No reports.
+ hr(size=1)
+ else
+ for report in reports
+ section.thread
+ +post(report, false, false, true)
+ hr(size=1)
+ include ../includes/actionfooter_globalmanage.pug
+ hr(size=1)
+ h4 All Bans:
+ form(action=`/forms/global/unban` method='POST' enctype='application/x-www-form-urlencoded')
+ input(type='hidden' name='_csrf' value=csrf)
+ if bans.length === 0
+ p No bans.
+ hr(size=1)
+ else
+ for ban in bans
+ section.thread
+ +ban(ban)
+ hr(size=1)
+ section.action-wrapper
+ input(type='submit', value='unban')
diff --git a/views/pages/login.pug b/views/pages/login.pug
index 85d15f09..fcd7d50f 100644
--- a/views/pages/login.pug
+++ b/views/pages/login.pug
@@ -7,6 +7,7 @@ block content
section.form-wrapper
form.form-post(action='/forms/login' method='POST')
input(type='hidden' name='_csrf' value=csrf)
+ input(type='hidden' name='redirect' value=redirect)
input#username(type='text', name='username', placeholder='username' maxlength='50')
input#password(type='password', name='password', placeholder='password' maxlength='100')
input(type='submit', value='submit')
diff --git a/views/pages/manage.pug b/views/pages/manage.pug
index a99461b1..f8236717 100644
--- a/views/pages/manage.pug
+++ b/views/pages/manage.pug
@@ -1,35 +1,36 @@
extends ../layout.pug
include ../mixins/post.pug
+include ../mixins/ban.pug
block head
- title Login
+ title Manage
block content
include ../includes/boardheader.pug
- hr(size=1)
- h4.board-description Management Panel
- form(action='/forms/board/'+board._id+'/posts' method='POST' enctype='application/x-www-form-urlencoded')
+ h4 Reports:
+ form(action=`/forms/board/${board._id}/actions` method='POST' enctype='application/x-www-form-urlencoded')
input(type='hidden' name='_csrf' value=csrf)
- if posts.length === 0
+ if reports.length === 0
p No reports.
hr(size=1)
- for post in posts
- section.thread
- +post(board, post)
+ else
+ for report in reports
+ section.thread
+ +post(report, false, true)
+ hr(size=1)
+ include ../includes/actionfooter_manage.pug
hr(size=1)
- section.action-wrapper
- span
- label
- input.post-check(type='checkbox', name='delete' value=1)
- | Delete
- span
- label
- input.post-check(type='checkbox', name='spoiler' value=1)
- | Spoiler
- span
- label
- input.post-check(type='checkbox', name='dismiss' value=1)
- | Dismiss
- input(type='submit', value='submit')
-
-
+ h4 Bans:
+ form(action=`/forms/board/${board._id}/unban` method='POST' enctype='application/x-www-form-urlencoded')
+ input(type='hidden' name='_csrf' value=csrf)
+ if bans.length === 0
+ p No bans.
+ hr(size=1)
+ else
+ for ban in bans
+ section.thread
+ +ban(ban)
+ hr(size=1)
+ section.action-wrapper
+ input(type='submit', value='unban')
+
diff --git a/views/pages/message.pug b/views/pages/message.pug
index dab4ab0c..70fb77c1 100644
--- a/views/pages/message.pug
+++ b/views/pages/message.pug
@@ -7,6 +7,10 @@ block content
h1 #{title}
if message
blockquote #{message}
+ if messages
+ ul
+ each msg in messages
+ li #{msg}
if errors
ul
each error in errors
diff --git a/views/pages/thread.pug b/views/pages/thread.pug
index cb23c2ba..465040f2 100644
--- a/views/pages/thread.pug
+++ b/views/pages/thread.pug
@@ -11,15 +11,14 @@ block head
block content
include ../includes/boardheader.pug
- hr(size=1)
include ../includes/postform.pug
.mode Posting mode: Reply [#[a.no-decoration(href=`/${board._id}`) Go Back]]
hr(size=1)
- form(action='/forms/board/'+board._id+'/posts' method='POST' enctype='application/x-www-form-urlencoded')
+ 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(board, thread)
+ +post(thread)
for post in thread.replies
- +post(board, post)
+ +post(post)
hr(size=1)
- include ../includes/deletefooter.pug
+ include ../includes/actionfooter.pug
diff --git a/wipe.js b/wipe.js
index 59c62519..b9e39de3 100644
--- a/wipe.js
+++ b/wipe.js
@@ -1,6 +1,6 @@
'use strict';
-const Mongo = require(__dirname+'/helpers/db.js')
+const Mongo = require(__dirname+'/db/db.js')
, util = require('util')
, path = require('path')
, fs = require('fs')
@@ -10,10 +10,11 @@ const Mongo = require(__dirname+'/helpers/db.js')
(async () => {
console.log('connecting to db...')
await Mongo.connect();
- const Boards = require(__dirname+'/db-models/boards.js')
- , Posts = require(__dirname+'/db-models/posts.js')
- , Trips = require(__dirname+'/db-models/trips.js')
- , Accounts = require(__dirname+'/db-models/accounts.js');
+ const Boards = require(__dirname+'/db/boards.js')
+ , Posts = require(__dirname+'/db/posts.js')
+ , Bans = require(__dirname+'/db/bans.js')
+ , Trips = require(__dirname+'/db/trips.js')
+ , Accounts = require(__dirname+'/db/accounts.js');
console.log('deleting accounts')
await Accounts.deleteAll();
console.log('deleting posts')
@@ -24,6 +25,8 @@ const Mongo = require(__dirname+'/helpers/db.js')
await Boards.deleteIncrement('b');
await Boards.deleteAll();
await Trips.deleteAll();
+ console.log('deleting bans');
+ await Bans.deleteAll();
console.log('adding b and pol')
await Boards.insertOne({
_id: 'pol',
@@ -40,6 +43,8 @@ const Mongo = require(__dirname+'/helpers/db.js')
moderators: [],
})
console.log('creating indexes')
+ await Bans.db.dropIndexes();
+ await Bans.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 });
await Posts.db.dropIndexes();
//these are fucked
await Posts.db.createIndex({
@@ -61,6 +66,15 @@ const Mongo = require(__dirname+'/helpers/db.js')
}
}
});
+ await Posts.db.createIndex({
+ 'globalreports.0': 1
+ }, {
+ partialFilterExpression: {
+ 'globalreports.0': {
+ '$exists': true
+ }
+ }
+ });
await readdir('static/img/').then(async files => {
await Promise.all(files.map(async file => {
if (file != 'spoiler.png')