Merge pull request #13 from fatchan/bans

add bans + numerous other improvements
merge-requests/208/head
Tom 5 years ago committed by GitHub
commit 530dd7d1c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 383
      controllers/forms.js
  2. 46
      controllers/pages.js
  3. 2
      db/accounts.js
  4. 66
      db/bans.js
  5. 20
      db/boards.js
  6. 0
      db/db.js
  7. 83
      db/posts.js
  8. 2
      db/trips.js
  9. 20
      helpers/bancheck.js
  10. 6
      helpers/check-auth.js
  11. 2
      helpers/files/video-thumbnail.js
  12. 9
      helpers/has-perms.js
  13. 16
      helpers/hasperms.js
  14. 16
      helpers/haspermsmiddleware.js
  15. 8
      helpers/isloggedin.js
  16. 45
      helpers/markdown.js
  17. 21
      helpers/number-converter.js
  18. 45
      helpers/quotes.js
  19. 2
      helpers/tripcode.js
  20. 17
      models/api/get-boards.js
  21. 20
      models/api/get-catalog.js
  22. 20
      models/api/get-recent.js
  23. 20
      models/api/get-thread.js
  24. 40
      models/forms/ban-poster.js
  25. 67
      models/forms/delete-post.js
  26. 34
      models/forms/dismiss-report.js
  27. 25
      models/forms/dismissglobalreport.js
  28. 31
      models/forms/edit-post.js
  29. 23
      models/forms/globalreportpost.js
  30. 21
      models/forms/login.js
  31. 26
      models/forms/make-post.js
  32. 16
      models/forms/register.js
  33. 13
      models/forms/removebans.js
  34. 21
      models/forms/report-post.js
  35. 67
      models/forms/spoiler-post.js
  36. 17
      models/pages/board.js
  37. 5
      models/pages/catalog.js
  38. 24
      models/pages/globalmanage.js
  39. 5
      models/pages/home.js
  40. 5
      models/pages/login.js
  41. 17
      models/pages/manage.js
  42. 11
      models/pages/register.js
  43. 5
      models/pages/thread.js
  44. 11
      server.js
  45. 135
      static/css/style.css
  46. 31
      views/includes/actionfooter.pug
  47. 24
      views/includes/actionfooter_globalmanage.pug
  48. 32
      views/includes/actionfooter_manage.pug
  49. 7
      views/includes/boardheader.pug
  50. 15
      views/includes/deletefooter.pug
  51. 8
      views/includes/navbar.pug
  52. 6
      views/includes/pages.pug
  53. 38
      views/includes/postform.pug
  54. 18
      views/mixins/ban.pug
  55. 5
      views/mixins/catalogtile.pug
  56. 21
      views/mixins/post.pug
  57. 14
      views/pages/ban.pug
  58. 18
      views/pages/board.pug
  59. 35
      views/pages/globalmanage.pug
  60. 1
      views/pages/login.pug
  61. 49
      views/pages/manage.pug
  62. 4
      views/pages/message.pug
  63. 9
      views/pages/thread.pug
  64. 24
      wipe.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;

@ -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);

@ -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');

@ -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({});
},
}

@ -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();

@ -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({

@ -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 = {

@ -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();
}

@ -1,6 +0,0 @@
'use strict';
module.exports = (req, res, next) => {
if (req.session.authenticated === true) return next()
res.redirect('/login')
}

@ -14,7 +14,7 @@ module.exports = (filename) => {
count: 1,
filename: `thumb-${filename.split('.')[0]}.png`,
folder: uploadDirectory,
size: '128x128'
size: '128x?'
});
});

@ -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
}

@ -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
)
)
)
}

@ -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();
}

@ -0,0 +1,8 @@
'use strict';
module.exports = (req, res, next) => {
if (req.session.authenticated === true) {
return next();
}
res.redirect('/login');
}

@ -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 `<span class='redtext'>&lt;${red}</span>`;
text = text.replace(redtextRegex, (match, redtext) => {
return `<span class='redtext'>&lt;${redtext}</span>`;
});
//greentext
text = text.replace(greentextRegex, (match) => {
const green = match.substring(1);
return `<span class='greentext'>&gt;${green}</span>`;
text = text.replace(greentextRegex, (match, greentext) => {
return `<span class='greentext'>&gt;${greentext}</span>`;
});
//links
@ -28,30 +26,25 @@ module.exports = (board, thread, text) => {
return `<a href="${match}">${match}</a>`;
});
//quotes
text = text.replace(quoteRegex, (match) => {
const quotenum = match.substring(2);
return `<a class='quote' href='/${board}/thread/${thread}#${quotenum}'>&gt;&gt;${quotenum}</a>`;
});
//bold
text = text.replace(boldRegex, (match) => {
const bold = match.substring(2, match.length-2);
text = text.replace(boldRegex, (match, bold) => {
return `<strong>${bold}</strong>`;
});
//italic
text = text.replace(italicRegex, (match) => {
const italic = match.substring(2, match.length-2);
return `<italic>${italic}</em>`;
text = text.replace(italicRegex, (match, italic) => {
return `<em>${italic}</em>`;
});
//spoilers
text = text.replace(spoilerRegex, (match) => {
const spoiler = match.substring(1, match.length-1);
text = text.replace(spoilerRegex, (match, spoiler) => {
return `<span class='spoiler'>${spoiler}</span>`;
});
text = text.replace(codeRegex, (match, code) => {
return `<span class='code'>${code.trim()}</span>`;
});
return text;
}

@ -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();
}

@ -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 `<a class='quote' href='/${board}/thread/${postThreadObject[quotenum]}#${quotenum}'>&gt;&gt;${quotenum}</a>`;
}
return match;
});
return text;
}

@ -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) => {

@ -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)
}

@ -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)
}

@ -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);
}

@ -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)
}

@ -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`;
}

@ -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`
}

@ -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`;
}

@ -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`;
}

@ -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);
}

@ -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`
}

@ -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'
});
}

@ -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);
}

@ -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')
}

@ -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`;
}

@ -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`
}

@ -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`
}

@ -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,
});
}

@ -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

@ -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,
});
}

@ -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

@ -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,
});
}

@ -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,
});
}

@ -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', {

@ -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) {

@ -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

@ -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;
}
}

@ -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')

@ -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')

@ -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')

@ -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}

@ -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')

@ -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

@ -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}

@ -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')

@ -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)

@ -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}

@ -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}

@ -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)

@ -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

@ -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')

@ -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')

@ -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')

@ -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

@ -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

@ -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')

Loading…
Cancel
Save