generate and save html to disk. actions that would cause a page to change delete the html. on the next visit, nginx will try_files, else pass to the backend which will generate the page again. CURRENTLY DOES NOT SUPPORT POST ACTIONS e.g. deletes, spoiler, sticky, etc will not cause pages to be deleted for future rebuilding. thats coming in next commits. consider this the start of actual smart building strategy to prevent templating and db hits unnecessarily. where its possible to serve a plain html page, we will do so.

merge-requests/208/head
fatchan 5 years ago
parent d956a7fd53
commit a818a25e91
  1. 43
      controllers/forms.js
  2. 20
      controllers/pages.js
  3. 2
      db/boards.js
  4. 49
      db/posts.js
  5. 6
      gulp/res/css/style.css
  6. 6
      helpers/captchaverify.js
  7. 7
      helpers/files/deletefailed.js
  8. 8
      helpers/files/deletepostfiles.js
  9. 4
      helpers/files/file-check-mime-types.js
  10. 2
      helpers/isloggedin.js
  11. 8
      helpers/markdown.js
  12. 8
      helpers/quotes.js
  13. 10
      helpers/writepagehtml.js
  14. 8
      models/forms/actionhandler.js
  15. 20
      models/forms/changepassword.js
  16. 10
      models/forms/deletebanners.js
  17. 9
      models/forms/deletepostsfiles.js
  18. 7
      models/forms/login.js
  19. 41
      models/forms/make-post.js
  20. 4
      models/forms/register.js
  21. 2
      models/forms/uploadbanners.js
  22. 18
      models/pages/board.js
  23. 15
      models/pages/catalog.js
  24. 14
      models/pages/changepassword.js
  25. 7
      models/pages/home.js
  26. 17
      models/pages/login.js
  27. 3
      models/pages/manage.js
  28. 16
      models/pages/register.js
  29. 35
      models/pages/thread.js
  30. 26
      package-lock.json
  31. 3
      package.json
  32. 2
      server.js
  33. 2
      views/includes/boardheader.pug
  34. 2
      views/includes/footer.pug
  35. 7
      views/includes/navbar.pug
  36. 15
      views/includes/pages.pug
  37. 13
      views/includes/postform.pug
  38. 4
      views/mixins/catalogtile.pug
  39. 2
      views/mixins/post.pug
  40. 4
      views/pages/board.pug
  41. 4
      views/pages/catalog.pug
  42. 5
      views/pages/login.pug
  43. 2
      views/pages/register.pug
  44. 8
      views/pages/thread.pug
  45. 22
      wipe.js

@ -17,16 +17,17 @@ const express = require('express')
, changePassword = require(__dirname+'/../models/forms/changepassword.js')
, registerAccount = require(__dirname+'/../models/forms/register.js')
, checkPermsMiddleware = require(__dirname+'/../helpers/haspermsmiddleware.js')
, checkPerms = require(__dirname+'/../helpers/hasperms.js')
, paramConverter = require(__dirname+'/../helpers/paramconverter.js')
, banCheck = require(__dirname+'/../helpers/bancheck.js')
, deletePostFiles = require(__dirname+'/../helpers/files/deletepostfiles.js')
, verifyCaptcha = require(__dirname+'/../helpers/captchaverify.js')
, actionHandler = require(__dirname+'/../models/forms/actionhandler.js')
, csrf = require(__dirname+'/../helpers/csrfmiddleware.js');
, csrf = require(__dirname+'/../helpers/csrfmiddleware.js')
, actionChecker = require(__dirname+'/../helpers/actionchecker.js');
// login to account
router.post('/login', csrf, (req, res, next) => {
router.post('/login', (req, res, next) => {
const errors = [];
@ -50,7 +51,7 @@ router.post('/login', csrf, (req, res, next) => {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/login'
'redirect': '/login.html'
})
}
@ -98,7 +99,7 @@ router.post('/changepassword', verifyCaptcha, async (req, res, next) => {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/changepassword'
'redirect': '/changepassword.html'
})
}
@ -144,7 +145,7 @@ router.post('/register', verifyCaptcha, (req, res, next) => {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/register'
'redirect': '/register.html'
})
}
@ -153,7 +154,7 @@ router.post('/register', verifyCaptcha, (req, res, next) => {
});
// make new post
router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, async (req, res, next) => {
router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verifyCaptcha, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@ -194,7 +195,7 @@ router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, async
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread : ''}`
'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
})
}
@ -232,13 +233,13 @@ router.post('/board/:board/settings', csrf, Boards.exists, checkPermsMiddleware,
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
})
}
return res.status(501).render('message', {
'title': 'Not implemented',
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
})
});
@ -266,7 +267,7 @@ router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddlewar
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
})
}
@ -292,7 +293,7 @@ router.post('/board/:board/deletebanners', csrf, Boards.exists, checkPermsMiddle
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
})
}
@ -301,7 +302,7 @@ router.post('/board/:board/deletebanners', csrf, Boards.exists, checkPermsMiddle
return res.status(400).render('message', {
'title': 'Bad request',
'message': 'Invalid banners selected',
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
})
}
}
@ -333,7 +334,7 @@ router.post('/board/:board/unban', csrf, Boards.exists, checkPermsMiddleware, pa
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
});
}
@ -347,7 +348,7 @@ router.post('/board/:board/unban', csrf, Boards.exists, checkPermsMiddleware, pa
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/${req.params.board}/manage`
'redirect': `/${req.params.board}/manage.html`
});
});
@ -387,7 +388,7 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': '/globalmanage'
'redirect': '/globalmanage.html'
})
}
@ -397,7 +398,7 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
return res.status(404).render('message', {
'title': 'Not found',
'errors': 'Selected posts not found',
'redirect': '/globalmanage'
'redirect': '/globalmanage.html'
})
}
@ -414,7 +415,7 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
}
messages.push(message);
}
if (hasPerms && req.body.delete_ip_global) {
if (req.body.delete_ip_global) {
const deletePostIps = posts.map(x => x.ip);
const deleteIpPosts = await Posts.db.find({
'ip': {
@ -476,7 +477,7 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': '/globalmanage'
'redirect': '/globalmanage.html'
});
});
@ -493,7 +494,7 @@ router.post('/global/unban', csrf, checkPermsMiddleware, paramConverter, async(r
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/globalmanage`
'redirect': `/globalmanage.html`
});
}
@ -507,7 +508,7 @@ router.post('/global/unban', csrf, checkPermsMiddleware, paramConverter, async(r
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/globalmanage`
'redirect': `/globalmanage.html`
});
});

@ -21,19 +21,19 @@ const express = require('express')
, thread = require(__dirname+'/../models/pages/thread.js');
//homepage with board list
router.get('/index', home);
router.get('/index.html', home);
//login page
router.get('/login', csrf, login);
router.get('/login.html', login);
//registration page
router.get('/register', register);
router.get('/register.html', register);
//change password page
router.get('/changepassword', changePassword);
router.get('/changepassword.html', changePassword);
//logout
router.get('/logout', csrf, isLoggedIn, (req, res, next) => {
router.get('/logout', isLoggedIn, (req, res, next) => {
//remove session
req.session.destroy();
@ -48,19 +48,19 @@ router.get('/captcha', captcha);
router.get('/banners', banners);
//board manage page
router.get('/:board/manage', Boards.exists, isLoggedIn, hasPerms, csrf, manage);
router.get('/:board/manage.html', Boards.exists, isLoggedIn, hasPerms, csrf, manage);
//board manage page
router.get('/globalmanage', isLoggedIn, hasPerms, csrf, globalManage);
router.get('/globalmanage.html', isLoggedIn, hasPerms, csrf, globalManage);
// board page/recents
router.get('/:board/(:page([2-9]*|index))?', Boards.exists, paramConverter, board);
router.get('/:board/:page([2-9]*|index).html', Boards.exists, paramConverter, board);
// thread view page
router.get('/:board/thread/:id(\\d+)', Boards.exists, paramConverter, thread);
router.get('/:board/thread/:id(\\d+).html', Boards.exists, paramConverter, thread);
// board catalog page
router.get('/:board/catalog', Boards.exists, catalog);
router.get('/:board/catalog.html', Boards.exists, catalog);
module.exports = router;

@ -78,7 +78,7 @@ module.exports = {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'You do not have permission to manage this board',
'redirect': '/login'
'redirect': '/login.html'
});
},

@ -9,6 +9,16 @@ module.exports = {
db,
getBeforeCount: (board, thread) => {
return db.countDocuments({
'board': board,
'thread': null,
'bumped': {
'$gt': thread.bumped
}
});
},
getRecent: async (board, page) => {
// get all thread posts (posts with null thread id)
const threads = await db.find({
@ -331,27 +341,28 @@ module.exports = {
}).sort({
'sticky': -1,
'bumped': -1
}).skip(threadLimit).toArray(); //100 therads in board limit for now
}).skip(threadLimit).toArray();
//if there are any
if (threads.length > 0) {
//get the postIds
const threadIds = threads.map(thread => thread.postId);
//get all the posts from those threads
const threadPosts = await module.exports.getMultipleThreadPosts(board, threadIds);
//combine them
const postsAndThreads = threads.concat(threadPosts);
//get the filenames and delete all the files
let fileNames = [];
postsAndThreads.forEach(post => {
fileNames = fileNames.concat(post.files.map(x => x.filename))
});
if (fileNames.length > 0) {
await deletePostFiles(fileNames);
}
//get the mongoIds and delete them all
const postMongoIds = postsAndThreads.map(post => Mongo.ObjectId(post._id));
await module.exports.deleteMany(postMongoIds);
if (threads.length === 0) {
return;
}
//get the postIds
const threadIds = threads.map(thread => thread.postId);
//get all the posts from those threads
const threadPosts = await module.exports.getMultipleThreadPosts(board, threadIds);
//combine them
const postsAndThreads = threads.concat(threadPosts);
//get the filenames and delete all the files
let fileNames = [];
postsAndThreads.forEach(post => {
fileNames = fileNames.concat(post.files.map(x => x.filename))
});
if (fileNames.length > 0) {
await deletePostFiles(fileNames);
}
//get the mongoIds and delete them all
const postMongoIds = postsAndThreads.map(post => Mongo.ObjectId(post._id));
return module.exports.deleteMany(postMongoIds);
},
deleteMany: (ids) => {

@ -156,8 +156,8 @@ object {
font-weight: bold;
}
.redtext {
color: maroon;
.pinktext {
color: #E0727F;
}
.greentext {
@ -254,7 +254,7 @@ td, th {
display: flex;
flex-direction: column;
max-width: 100%;
/*margin-top: 10px;*/
width: 400px;
}
.togglable {

@ -2,9 +2,7 @@
const Captchas = require(__dirname+'/../db/captchas.js')
, Mongo = require(__dirname+'/../db/db.js')
, util = require('util')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
, remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../helpers/uploadDirectory.js');
module.exports = async (req, res, next) => {
@ -46,7 +44,7 @@ module.exports = async (req, res, next) => {
//it was correct, so delete the file, the cookie and continue
res.clearCookie('captchaid');
await unlink(`${uploadDirectory}captcha/${captchaId}.jpg`)
await remove(`${uploadDirectory}captcha/${captchaId}.jpg`)
return next();

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

@ -1,8 +1,6 @@
'use strict';
const util = require('util')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
const remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
module.exports = (fileNames) => {
@ -11,8 +9,8 @@ module.exports = (fileNames) => {
return Promise.all(fileNames.map(async filename => {
//dont question it.
return Promise.all([
unlink(`${uploadDirectory}img/${filename}`),
unlink(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.jpg`)
remove(`${uploadDirectory}img/${filename}`),
remove(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.jpg`)
]).catch(e => console.error) //ignore for now
}));

@ -4,12 +4,12 @@ const imageMimeTypes = new Set([
'image/jpeg',
'image/pjpeg',
'image/png',
'image/bmp',
'image/gif',
'image/webp',
]);
const videoMimeTypes = new Set([
'image/webp',
'image/bmp',
'video/mp4',
'video/webm',
]);

@ -4,5 +4,5 @@ module.exports = (req, res, next) => {
if (req.session.authenticated === true) {
return next();
}
res.redirect('/login');
res.redirect('/login.html');
}

@ -2,7 +2,7 @@
const Posts = require(__dirname+'/../db/posts.js')
, greentextRegex = /^>([^>].+)/gm
, redtextRegex = /^<([^<].+)/gm
, pinktextRegex = /^<([^<].+)/gm
, boldRegex = /""(.+)""/gm
, titleRegex = /==(.+)==/gm
, italicRegex = /__(.+)__/gm
@ -13,9 +13,9 @@ const Posts = require(__dirname+'/../db/posts.js')
module.exports = (board, thread, text) => {
//redtext
text = text.replace(redtextRegex, (match, redtext) => {
return `<span class='redtext'>&lt;${redtext}</span>`;
//pinktext
text = text.replace(pinktextRegex, (match, pinktext) => {
return `<span class='pinktext'>&lt;${pinktext}</span>`;
});
//greentext

@ -75,7 +75,7 @@ module.exports = async (board, text) => {
text = text.replace(quoteRegex, (match) => {
const quotenum = +match.substring(2);
if (postThreadIdMap[board] && postThreadIdMap[board][quotenum]) {
return `<a class='quote' href='/${board}/thread/${postThreadIdMap[board][quotenum]}#${quotenum}'>&gt;&gt;${quotenum}</a>`;
return `<a class='quote' href='/${board}/thread/${postThreadIdMap[board][quotenum]}.html#${quotenum}'>&gt;&gt;${quotenum}</a>`;
}
return match;
});
@ -86,9 +86,9 @@ module.exports = async (board, text) => {
const quoteboard = quote[1];
const quotenum = +quote[2];
if (postThreadIdMap[quoteboard] && postThreadIdMap[quoteboard][quotenum]) {
return `<a class='quote' href='/${quoteboard}/thread/${postThreadIdMap[quoteboard][quotenum]}#${quotenum}'>&gt;&gt;&gt;/${quoteboard}/${quotenum}</a>`;
} else if (postThreadIdMap[quoteboard] && quotenum === 0) {
return `<a class='quote' href='/${quoteboard}/'>&gt;&gt;&gt;/${quoteboard}/</a>`;
return `<a class='quote' href='/${quoteboard}/thread/${postThreadIdMap[quoteboard][quotenum]}.html#${quotenum}'>&gt;&gt;&gt;/${quoteboard}/${quotenum}</a>`;
} else if (!quote[2]) {
return `<a class='quote' href='/${quoteboard}/index.html'>&gt;&gt;&gt;/${quoteboard}/</a>`;
}
return match;
});

@ -1,14 +1,12 @@
'use strict';
const util = require('util')
, fs = require('fs')
const outputFile = require('fs-extra').outputFile
, pug = require('pug')
, path = require('path')
, writeFile = util.promisify(fs.writeFile)
, uploadDirectory = require(__dirname+'/uploadDirectory.js')
, pugDirectory = path.join(__dirname+'/../views/pages');
, pugDirectory = path.join(__dirname+'/../views/pages/');
module.exports = async (htmlName, pugName, pugVars) => {
const html = pug.renderFile(`${pugDirectory}/${pugName}`, pugVars);
return writeFile(`${uploadDirectory}html/${htmlName}`, html);
const html = pug.renderFile(`${pugDirectory}${pugName}`, pugVars);
return outputFile(`${uploadDirectory}html/${htmlName}`, html);
};

@ -57,7 +57,7 @@ module.exports = async (req, res, next) => {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
'redirect': `/${req.params.board}`
'redirect': `/${req.params.board}/`
})
}
@ -66,7 +66,7 @@ module.exports = async (req, res, next) => {
return res.status(404).render('message', {
'title': 'Not found',
'error': 'Selected posts not found',
'redirect': `/${req.params.board}`
'redirect': `/${req.params.board}/`
})
}
@ -88,7 +88,7 @@ module.exports = async (req, res, next) => {
return res.status(403).render('message', {
'title': 'Forbidden',
'error': 'Password did not match any selected posts',
'redirect': `/${req.params.board}`
'redirect': `/${req.params.board}/`
});
}
} else {
@ -247,7 +247,7 @@ module.exports = async (req, res, next) => {
return res.render('message', {
'title': 'Success',
'messages': messages,
'redirect': `/${req.params.board}`
'redirect': `/${req.params.board}/`
});
}

@ -17,7 +17,7 @@ module.exports = async (req, res, next) => {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
'redirect': redirect ? `/login?redirect=${redirect}` : '/changepassword'
'redirect': '/changepassword.html'
});
}
@ -25,16 +25,16 @@ module.exports = async (req, res, next) => {
const passwordMatch = await bcrypt.compare(password, account.passwordHash);
//if hashes matched
if (passwordMatch === true) {
//change the password
await Accounts.changePassword(username, newPassword);
return res.redirect('/login');
if (passwordMatch === false) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
'redirect': '/changepassword.html'
});
}
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
'redirect': redirect ? `/login?redirect=${redirect}` : '/login'
});
//change the password
await Accounts.changePassword(username, newPassword);
return res.redirect('/login.html');
}

@ -1,19 +1,15 @@
'use strict';
const uuidv4 = require('uuid/v4')
, path = require('path')
, util = require('util')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
const remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, Boards = require(__dirname+'/../../db/boards.js');
module.exports = async (req, res, next) => {
const redirect = `/${req.params.board}/manage`
const redirect = `/${req.params.board}/manage.html`
await Promise.all(req.body.checkedbanners.map(async filename => {
unlink(`${uploadDirectory}banner/${filename}`);
remove(`${uploadDirectory}banner/${filename}`);
}));
// i dont think there is a way to get the number of array items removed with $pullAll

@ -1,9 +1,6 @@
'use strict';
const path = require('path')
, util = require('util')
, fs = require('fs')
, unlink = util.promisify(fs.unlink)
const remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
module.exports = async (posts) => {
@ -24,8 +21,8 @@ module.exports = async (posts) => {
await Promise.all(fileNames.map(async filename => {
//dont question it.
return Promise.all([
unlink(`${uploadDirectory}img/${filename}`),
unlink(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.png`)
remove(`${uploadDirectory}img/${filename}`),
remove(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.png`)
])
}));

@ -7,7 +7,6 @@ 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;
@ -22,7 +21,7 @@ module.exports = async (req, res, next) => {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
'redirect': redirect ? `/login?redirect=${redirect}` : '/login'
'redirect': '/login.html'
});
}
@ -45,14 +44,14 @@ module.exports = async (req, res, next) => {
req.session.authenticated = true;
//successful login
return res.redirect(redirect || '/');
return res.redirect('/');
}
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Incorrect username or password',
'redirect': redirect ? `/login?redirect=${redirect}` : '/login'
'redirect': '/login.html'
});
}

@ -5,6 +5,7 @@ const uuidv4 = require('uuid/v4')
, util = require('util')
, crypto = require('crypto')
, randomBytes = util.promisify(crypto.randomBytes)
, remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, Posts = require(__dirname+'/../../db/posts.js')
, getTripCode = require(__dirname+'/../../helpers/tripcode.js')
@ -31,7 +32,7 @@ const uuidv4 = require('uuid/v4')
module.exports = async (req, res, next, numFiles) => {
// check if this is responding to an existing thread
let redirect = `/${req.params.board}`
let redirect = `/${req.params.board}/`
let salt = null;
let thread = null;
const hasPerms = permsCheck(req, res);
@ -46,7 +47,7 @@ module.exports = async (req, res, next, numFiles) => {
});
}
salt = thread.salt;
redirect += `/thread/${req.body.thread}`
redirect += `thread/${req.body.thread}.html`
if (thread.locked && !hasPerms) {
return res.status(400).render('message', {
'title': 'Bad request',
@ -62,6 +63,13 @@ module.exports = async (req, res, next, numFiles) => {
});
}
}
if (numFiles > res.locals.board.settings.maxFiles) {
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Too many files. Max files per post is ${res.locals.board.settings.maxFiles}.`,
'redirect': redirect
});
}
let files = [];
// if we got a file
if (numFiles > 0) {
@ -214,11 +222,36 @@ module.exports = async (req, res, next, numFiles) => {
}
const postId = await Posts.insertOne(req.params.board, data, thread);
if (!data.thread) { //if we just added a new thread, prune any old ones
if (!data.thread) {
//if we just added a new thread, prune any old ones
await Posts.pruneOldThreads(req.params.board, res.locals.board.settings.threadLimit);
}
const successRedirect = `/${req.params.board}/thread/${req.body.thread || postId}#${postId}`;
//now we need to delete outdated html
const removePromises = []
//always need to refresh catalog
removePromises.push(remove(`${uploadDirectory}html/${req.params.board}/catalog.html`));
if (data.thread) {
//refresh the thread itself
removePromises.push(remove(`${uploadDirectory}html/${req.params.board}/thread/${req.body.thread}.html`));
if (!data.sage) {
//bumping a thread, so delete all pages above it
const numThreadsBefore = await Posts.getBeforeCount(req.params.board, thread);
const pagesToRemove = Math.ceil(numThreadsBefore/10);
for (let i = 1; i <= pagesToRemove; i++) {
removePromises.push(remove(`${uploadDirectory}html/${req.params.board}/${i == 1 ? 'index' : i}.html`));
}
}
} else {
//new thread, remove all pages
for (let i = 1; i <= Math.ceil(res.locals.board.settings.threadLimit/10); i++) {
removePromises.push(remove(`${uploadDirectory}html/${req.params.board}/${i == 1 ? 'index' : i}.html`));
}
}
await Promise.all(removePromises);
const successRedirect = `/${req.params.board}/thread/${req.body.thread || postId}.html#${postId}`;
return res.redirect(successRedirect);
}

@ -20,7 +20,7 @@ module.exports = async (req, res, next) => {
return res.status(409).render('message', {
'title': 'Conflict',
'message': 'Account with this username already exists',
'redirect': '/register'
'redirect': '/register.html'
});
}
@ -31,6 +31,6 @@ module.exports = async (req, res, next) => {
return next(err);
}
return res.redirect('/login')
return res.redirect('/login.html')
}

@ -11,7 +11,7 @@ const uuidv4 = require('uuid/v4')
module.exports = async (req, res, next, numFiles) => {
const redirect = `/${req.params.board}/manage`
const redirect = `/${req.params.board}/manage.html`
// check all mime types befoer we try saving anything
for (let i = 0; i < numFiles; i++) {

@ -1,26 +1,32 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')l
const Posts = require(__dirname+'/../../db/posts.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js');
module.exports = async (req, res, next) => {
const page = req.params.page === 'index' ? 1 : (req.params.page || 1);
let threads;
let pages;
let pageURL;
try {
pages = Math.ceil((await Posts.getPages(req.params.board)) / 10)
if (page > pages && pages > 0) {
return next();
}
threads = await Posts.getRecent(req.params.board, page);
pageURL = `${req.params.board}/${req.params.page}.html`;
await writePageHTML(pageURL, 'board.pug', {
board: res.locals.board,
threads: threads || [],
pages,
page
});
} catch (err) {
return next(err);
}
return res.render('board', {
threads: threads || [],
pages,
page,
});
return res.sendFile(`${uploadDirectory}html/${pageURL}`);
}

@ -1,20 +1,25 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js');
const Posts = require(__dirname+'/../../db/posts.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js');
module.exports = async (req, res, next) => {
// get all threads
let threads;
let pageURL;
try {
threads = await Posts.getCatalog(req.params.board);
pageURL = `${req.params.board}/catalog.html`;
await writePageHTML(pageURL, 'catalog.pug', {
board: res.locals.board,
threads: threads || [],
});
} catch (err) {
return next(err);
}
//render the page
res.render('catalog', {
threads: threads || [],
});
return res.sendFile(`${uploadDirectory}html/${pageURL}`);
}

@ -1,8 +1,16 @@
'use strict';
module.exports = (req, res, next) => {
const writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js');
//render the page
res.render('changepassword');
module.exports = async (req, res, next) => {
try {
await writePageHTML('changepassword.html', 'changepassword.pug');
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html/changepassword.html`);
}

@ -1,6 +1,8 @@
'use strict';
const Boards = require(__dirname+'/../../db/boards.js');
const Boards = require(__dirname+'/../../db/boards.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js');
module.exports = async (req, res, next) => {
@ -8,10 +10,11 @@ module.exports = async (req, res, next) => {
let boards;
try {
boards = await Boards.find();
await writePageHTML('index.html', 'home.pug', { boards });
} catch (err) {
return next(err);
}
res.render('home', { boards });
return res.sendFile(`${uploadDirectory}html/index.html`);
}

@ -1,11 +1,16 @@
'use strict';
module.exports = (req, res, next) => {
const writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js');
//render the page
res.render('login', {
csrf: req.csrfToken(),
redirect: req.query.redirect,
});
module.exports = async (req, res, next) => {
try {
await writePageHTML('login.html', 'login.pug');
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html/login.html`);
}

@ -1,7 +1,8 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')
, Bans = require(__dirname+'/../../db/bans.js');
, Bans = require(__dirname+'/../../db/bans.js')
, writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js');
module.exports = async (req, res, next) => {

@ -1,10 +1,16 @@
'use strict';
module.exports = (req, res, next) => {
const writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js');
//render the page
res.render('register', {
csrf: req.csrfToken()
});
module.exports = async (req, res, next) => {
try {
await writePageHTML('register.html', 'register.pug');
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html/register.html`);
}

@ -1,23 +1,28 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js');
const Posts = require(__dirname+'/../../db/posts.js')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, writePageHTML = require(__dirname+'/../../helpers/writepagehtml.js');
module.exports = async (req, res, next) => {
//get the recently bumped thread & preview posts
let thread;
try {
thread = await Posts.getThread(req.params.board, req.params.id);
} catch (err) {
return next(err);
}
//get the recently bumped thread & preview posts
let thread;
let threadURL;
try {
thread = await Posts.getThread(req.params.board, req.params.id);
if (!thread) {
return res.status(404).render('404');
}
threadURL = `${req.params.board}/thread/${req.params.id}.html`;
await writePageHTML(threadURL, 'thread.pug', {
board: res.locals.board,
thread
});
} catch (err) {
return next(err);
}
if (!thread) {
return res.status(404).render('404');
}
return res.sendFile(`${uploadDirectory}html/${threadURL}`);
//render the page
res.render('thread', {
thread
});
}

26
package-lock.json generated

@ -2083,6 +2083,16 @@
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
},
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"fs-minipass": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
@ -2826,8 +2836,7 @@
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
"dev": true
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"gulp": {
"version": "4.0.1",
@ -3572,6 +3581,14 @@
"dev": true,
"optional": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@ -5920,6 +5937,11 @@
"through2-filter": "^3.0.0"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

@ -15,6 +15,7 @@
"express-session": "^1.16.1",
"fluent-ffmpeg": "^2.1.2",
"fs": "0.0.1-security",
"fs-extra": "^7.0.1",
"helmet": "^3.16.0",
"mongodb": "^3.2.3",
"path": "^0.12.7",
@ -33,7 +34,7 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"wipe": "node reset.js && gulp"
"wipe": "node wipe.js && gulp"
},
"author": "",
"license": "ISC"

@ -53,7 +53,7 @@ const express = require('express')
if (req.method !== 'POST') {
return next();
}
if (!req.headers.referer || !req.headers.referer.startsWith('https://fatpeople.lol')) {
if (!req.headers.referer || !req.headers.referer.match(/^https:\/\/(www\.)?fatpeople\.lol/)) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'Invalid or missing "Referer" header. Are you posting from the correct URL?'

@ -1,6 +1,6 @@
section.board-header
if board.banners.length > 0
object.board-banner(data=`/banners?board=${board._id}` width='300' height='100')
a.no-decoration(href=`/${board._id}/index`)
a.no-decoration(href=`/${board._id}/index.html`)
h1.board-title /#{board._id}/ - #{board.name}
h4.board-description #{board.description}

@ -1,2 +1,2 @@
.footer
a(href='https://github.com/fatchan/jschan/') not lynxchan
a(href='https://github.com/fatchan/jschan/') not lynxchan™

@ -1,8 +1,5 @@
nav.navbar
a.nav-item(href='/') Home
a.nav-item.right(href='/logout') Logout
if board
a.nav-item.right(href=`/login?redirect=/${board._id}/index`) Login
a.nav-item.right(href=`/${board._id}/manage`) Manage
else
a.nav-item.right(href='/login') Login
a.nav-item.right(href=`/${board ? board._id+'/' : 'global'}manage.html`) Manage
a.nav-item.right(href='/login.html') Login

@ -1,15 +1,20 @@
| Page:
span
a(href=`/${board._id}/index`) [#{1}]
|
if page === 1
span
a(href=`/${board._id}/index.html`) [#{1}]
|
else
span
a(href=`/${board._id}/index.html`) #{1}
|
- for(let i = 2; i <= pages; i++)
if i === page
span
a(href=`/${board._id}/${i}`) [#{i}]
a(href=`/${board._id}/${i}.html`) [#{i}]
|
else
span
a(href=`/${board._id}/${i}`) #{i}
a(href=`/${board._id}/${i}.html`) #{i}
|
| |

@ -25,12 +25,13 @@ section.form-wrapper.flex-center.mv-10
section.postform-row
.postform-label Message
textarea#message(name='message', rows='5', autocomplete='off' maxlength='2000')
section.postform-row
.postform-label Files
input#file(type='file', name='file' multiple='multiple')
label.postform-style.ph-5.ml-1
input#spoiler(type='checkbox', name='spoiler', value='true')
| Spoiler
if board.settings.maxFiles !== 0
section.postform-row
.postform-label Files
input#file(type='file', name='file' multiple='multiple')
label.postform-style.ph-5.ml-1
input#spoiler(type='checkbox', name='spoiler', value='true')
| Spoiler
section.postform-row
.postform-label Password
input#password(type='password', name='password', autocomplete='off' placeholder='password for deleting post later' maxlength='50')

@ -1,13 +1,13 @@
mixin catalogtile(board, post, truncate)
article(class='catalog-tile')
- const postURL = `/${board._id}/thread/${post.postId}#${post.postId}`
- const postURL = `/${board._id}/thread/${post.postId}.html#${post.postId}`
a.catalog-tile-button(href=postURL) Open Thread
if post.subject
span: a.no-decoration.post-subject(href=postURL) #{post.subject}
.catalog-tile-content
if post.files.length > 0
.post-file-src
a(href=`/${board._id}/thread/${post.postId}#${post.postId}`)
a(href=postURL)
if post.spoiler
object(data='/img/spoiler.png' width='64' height='64')
else

@ -1,6 +1,6 @@
mixin post(post, truncate, manage=false, globalmanage=false)
article(id=post.postId class='post-container '+(post.thread ? '' : 'op'))
- const postURL = `/${post.board}/thread/${post.thread || post.postId}#${post.postId}`;
- const postURL = `/${post.board}/thread/${post.thread || post.postId}.html#${post.postId}`;
header.post-info
if globalmanage
input.post-check(type='checkbox', name='globalcheckedposts[]' value=post._id)

@ -12,7 +12,7 @@ block content
include ../includes/pages.pug
a(href='#bottom') [Bottom]
|
a(href=`/${board._id}/catalog`) [Catalog]
a(href=`/${board._id}/catalog.html`) [Catalog]
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)
@ -28,5 +28,5 @@ block content
nav.pages#bottom
a(href='#top') [Top]
|
a(href=`/${board._id}/catalog`) [Catalog]
a(href=`/${board._id}/catalog.html`) [Catalog]
include ../includes/actionfooter.pug

@ -9,7 +9,7 @@ block content
nav.pages#top
a(href='#bottom') [Bottom]
|
a(href=`/${board._id}/index`) [Return]
a(href=`/${board._id}/index.html`) [Return]
hr(size=1)
if threads.length === 0
p No posts.
@ -20,4 +20,4 @@ block content
nav.pages#bottom
a(href='#top') [Top]
|
a(href=`/${board._id}/index`) [Return]
a(href=`/${board._id}/index.html`) [Return]

@ -6,8 +6,7 @@ block head
block content
section.form-wrapper.flex-center.mv-10
form.form-post(action='/forms/login' method='POST')
input(type='hidden' name='_csrf' value=csrf)
input(type='hidden' name='redirect' value=redirect)
//input(type='hidden' name='_csrf' value=csrf)
section.postform-row
.postform-label Username
input#username(type='text', name='username', maxlength='50')
@ -15,5 +14,5 @@ block content
.postform-label Password
input#password(type='password', name='password', maxlength='100')
input(type='submit', value='submit')
p No account? #[a(href='/register') Register]
p No account? #[a(href='/register.html') Register]

@ -22,4 +22,4 @@ block content
img.captcha(src='/captcha' width=200 height=80)
input#captcha(type='text', name='captcha', autocomplete='off' placeholder='captcha text' maxlength='6')
input(type='submit', value='Register')
p Already have an account? #[a(href='/login') Login]
p Already have an account? #[a(href='/login.html') Login]

@ -16,9 +16,9 @@ block content
nav.pages#top
a(href='#bottom') [Bottom]
|
a(href=`/${board._id}/index`) [Return]
a(href=`/${board._id}/index.html`) [Return]
|
a(href=`/${board._id}/catalog`) [Catalog]
a(href=`/${board._id}/catalog.html`) [Catalog]
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)
@ -30,7 +30,7 @@ block content
nav.pages#bottom
a(href='#top') [Top]
|
a(href=`/${board._id}/index`) [Return]
a(href=`/${board._id}/index.html`) [Return]
|
a(href=`/${board._id}/catalog`) [Catalog]
a(href=`/${board._id}/catalog.html`) [Catalog]
include ../includes/actionfooter.pug

@ -24,14 +24,16 @@ const Mongo = require(__dirname+'/db/db.js')
console.log('deleting posts')
await Posts.deleteAll('pol');
await Posts.deleteAll('b');
await Posts.deleteAll('t');
console.log('deleting boards')
await Boards.deleteIncrement('pol');
await Boards.deleteIncrement('b');
await Boards.deleteIncrement('b');
await Boards.deleteIncrement('t');
await Boards.deleteAll();
await Trips.deleteAll();
console.log('deleting bans');
await Bans.deleteAll();
console.log('adding b and pol')
console.log('adding boards')
await Boards.insertOne({
_id: 'pol',
name: 'Politically Incorrect',
@ -64,6 +66,22 @@ const Mongo = require(__dirname+'/db/db.js')
defaultName: 'Anonymous',
}
})
await Boards.insertOne({
_id: 't',
name: 'text',
description: 'text only board',
owner: '',
moderators: [],
banners: [],
settings: {
forceAnon: true,
ids: false,
threadLimit: 100,
replyLimit: 300,
maxFiles: 0,
defaultName: 'Anonymous',
}
})
console.log('creating indexes')
await Bans.db.dropIndexes();
await Bans.db.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 });

Loading…
Cancel
Save