diff --git a/build.js b/build.js
index 303dc3c5..baef993b 100644
--- a/build.js
+++ b/build.js
@@ -5,9 +5,43 @@ const Posts = require(__dirname+'/db/posts.js')
, uploadDirectory = require(__dirname+'/helpers/uploadDirectory.js')
, render = require(__dirname+'/helpers/render.js');
+
+function addBacklinks(thread, preview) { //preview means this is not the full thread
+ const postMap = new Map()
+ postMap.set(thread.postId, thread)
+ for (let i = 0; i < thread.replies.length; i++) {
+ const reply = thread.replies[i];
+ postMap.set(reply.postId, reply);
+ }
+ for (let i = 0; i < thread.replies.length; i++) {
+ const reply = thread.replies[i];
+ if (!reply.quotes) continue;
+ for (let j = 0; j < reply.quotes.length; j++) {
+ const quote = reply.quotes[j];
+ if (postMap.has(quote)) {
+ const post = postMap.get(quote)
+ if (!post.backlinks) {
+ post.backlinks = [];
+ }
+ post.backlinks.push(reply.postId);
+ } else if (!preview) {
+ /*
+ quote was valid on post creation, but points to postID that has been deleted
+ or possibly removed from cyclical thread (whenever i implement those)
+ could re-markdown the post here to remove the quotes (or convert to greentext)
+ */
+ }
+ }
+ }
+}
+
module.exports = {
buildCatalog: async (board) => {
+//console.log('building catalog', `${board._id}/catalog.html`);
+ if (!board._id) {
+ board = await Boards.findOne(board);
+ }
const threads = await Posts.getCatalog(board._id);
return render(`${board._id}/catalog.html`, 'catalog.pug', {
board,
@@ -22,8 +56,11 @@ module.exports = {
}
const thread = await Posts.getThread(board._id, threadId)
if (!thread) {
- return; //this thread may have been an OP that was deleted during a rebuild
+ return; //this thread may have been an OP that was deleted
}
+
+ addBacklinks(thread, false);
+
return render(`${board._id}/thread/${threadId}.html`, 'thread.pug', {
board,
thread
@@ -36,6 +73,12 @@ module.exports = {
if (!maxPage) {
maxPage = Math.ceil((await Posts.getPages(board._id)) / 10);
}
+
+ for (let k = 0; k < threads.length; k++) {
+ const thread = threads[k];
+ addBacklinks(thread, true);
+ }
+
return render(`${board._id}/${page === 1 ? 'index' : page}.html`, 'board.pug', {
board,
threads,
@@ -56,6 +99,12 @@ module.exports = {
}
const difference = endpage-startpage + 1; //+1 because for single pagemust be > 0
const threads = await Posts.getRecent(board._id, startpage, difference*10);
+
+ for (let k = 0; k < threads.length; k++) {
+ const thread = threads[k];
+ addBacklinks(thread, true);
+ }
+
const buildArray = [];
for (let i = startpage; i <= endpage; i++) {
//console.log('multi building board page', `${board._id}/${i === 1 ? 'index' : i}.html`);
diff --git a/controllers/forms.js b/controllers/forms.js
index cf31baa2..4a663c01 100644
--- a/controllers/forms.js
+++ b/controllers/forms.js
@@ -4,30 +4,49 @@ const express = require('express')
, router = express.Router()
, Boards = require(__dirname+'/../db/boards.js')
, Posts = require(__dirname+'/../db/posts.js')
- , Captchas = require(__dirname+'/../db/captchas.js')
- , Trips = require(__dirname+'/../db/trips.js')
- , Bans = require(__dirname+'/../db/bans.js')
, Mongo = require(__dirname+'/../db/db.js')
, remove = require('fs-extra').remove
- , deletePosts = require(__dirname+'/../models/forms/delete-post.js')
- , spoilerPosts = require(__dirname+'/../models/forms/spoiler-post.js')
- , dismissGlobalReports = require(__dirname+'/../models/forms/dismissglobalreport.js')
- , banPoster = require(__dirname+'/../models/forms/ban-poster.js')
+ , upload = require('express-fileupload')
+ , path = require('path')
+ , postFiles = upload({
+ createParentPath: true,
+ safeFileNames: /[^\w-]+/g,
+ preserveExtension: 4,
+ limits: {
+ fileSize: 10 * 1024 * 1024,
+ files: 3
+ },
+ abortOnLimit: true,
+ useTempFiles: true,
+ tempFileDir: path.join(__dirname+'/../tmp/')
+ })
+ , bannerFiles = upload({
+ createParentPath: true,
+ safeFileNames: /[^\w-]+/g,
+ preserveExtension: 4,
+ limits: {
+ fileSize: 10 * 1024 * 1024,
+ files: 10
+ },
+ abortOnLimit: true,
+ useTempFiles: true,
+ tempFileDir: path.join(__dirname+'/../tmp/')
+ })
, removeBans = require(__dirname+'/../models/forms/removebans.js')
- , makePost = require(__dirname+'/../models/forms/make-post.js')
+ , makePost = require(__dirname+'/../models/forms/makepost.js')
+ , deleteTempFiles = require(__dirname+'/../helpers/files/deletetempfiles.js')
, uploadBanners = require(__dirname+'/../models/forms/uploadbanners.js')
, deleteBanners = require(__dirname+'/../models/forms/deletebanners.js')
, loginAccount = require(__dirname+'/../models/forms/login.js')
, 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')
- , deleteFailedFiles = require(__dirname+'/../helpers/files/deletefailed.js')
, actionChecker = require(__dirname+'/../helpers/actionchecker.js');
@@ -159,7 +178,7 @@ router.post('/register', verifyCaptcha, (req, res, next) => {
});
// make new post
-router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verifyCaptcha, async (req, res, next) => {
+router.post('/board/:board/post', Boards.exists, banCheck, postFiles, paramConverter, verifyCaptcha, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@@ -207,25 +226,18 @@ router.post('/board/:board/post', Boards.exists, banCheck, paramConverter, verif
}
if (errors.length > 0) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
- 'title': 'Bad request',
- 'errors': errors,
- 'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
- })
+ 'title': 'Bad request',
+ 'errors': errors,
+ 'redirect': `/${req.params.board}${req.body.thread ? '/thread/' + req.body.thread + '.html' : ''}`
+ });
}
try {
await makePost(req, res, next, numFiles);
} catch (err) {
- //handler errors here better
- if (numFiles > 0) {
- const fileNames = []
- for (let i = 0; i < req.files.file.length; i++) {
- remove(req.files.file[i].tempFilePath).catch(e => console.error);
- fileNames.push(req.files.file[i].filename);
- }
- deletePostFiles(fileNames).catch(err => console.error);
- }
+ await deleteTempFiles(req).catch(e => console.error);
return next(err);
}
@@ -236,8 +248,8 @@ router.post('/board/:board/settings', csrf, Boards.exists, checkPermsMiddleware,
const errors = [];
- if (req.body.default_name && req.body.default_name.length > 20) {
- errors.push('Must provide a message or file');
+ if (req.body.default_name && req.body.default_name.length < 1 || req.body.default_name.length > 50) {
+ errors.push('Anon name must be 1-50 characters');
}
if (typeof req.body.reply_limit === 'number' && (req.body.reply_limit < 1 || req.body.reply_limit > 1000)) {
errors.push('Reply Limit must be from 1-1000');
@@ -257,15 +269,19 @@ router.post('/board/:board/settings', csrf, Boards.exists, checkPermsMiddleware,
})
}
- return res.status(501).render('message', {
- 'title': 'Not implemented',
- 'redirect': `/${req.params.board}/manage.html`
- })
+ try {
+ return res.status(501).render('message', {
+ 'title': 'Not implemented',
+ 'redirect': `/${req.params.board}/manage.html`
+ })
+ } catch (err) {
+ return next(err);
+ }
});
//upload banners
-router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => {
+router.post('/board/:board/addbanners', bannerFiles, csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => {
let numFiles = 0;
if (req.files && req.files.file) {
@@ -282,11 +298,12 @@ router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddlewar
if (numFiles === 0) {
errors.push('Must provide a file');
}
- if (res.locals.board.banners.length > 100) {
- errors.push('Limit of 100 banners reached');
+ if (res.locals.board.banners.length+numFiles > 100) {
+ errors.push('Number of uploads would exceed 100 banner limit');
}
if (errors.length > 0) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
@@ -297,14 +314,7 @@ router.post('/board/:board/addbanners', csrf, Boards.exists, checkPermsMiddlewar
try {
await uploadBanners(req, res, next, numFiles);
} catch (err) {
- const fileNames = [];
- if (numFiles > 0) {
- for (let i = 0; i < req.files.file.length; i++) {
- remove(req.files.file[i].tempFilePath).catch(e => console.error);
- fileNames.push(req.files.file[i].filename);
- }
- }
- deleteFailedFiles(fileNames, 'banner').catch(e => console.error);
+ await deleteTempFiles(req).catch(e => console.error);
return next(err);
}
@@ -346,44 +356,73 @@ router.post('/board/:board/deletebanners', csrf, Boards.exists, checkPermsMiddle
});
-//report/delete/spoiler/ban
-router.post('/board/:board/actions', Boards.exists, banCheck, paramConverter, verifyCaptcha, actionHandler); //Captcha on regular actions
-router.post('/board/:board/modactions', csrf, Boards.exists, checkPermsMiddleware, paramConverter, actionHandler); //CSRF for mod actions
-
-//unban
-router.post('/board/:board/unban', csrf, Boards.exists, checkPermsMiddleware, paramConverter, async (req, res, next) => {
+//actions for a specific board
+router.post('/board/:board/actions', Boards.exists, banCheck, paramConverter, verifyCaptcha, boardActionController); //Captcha on regular actions
+router.post('/board/:board/modactions', csrf, Boards.exists, checkPermsMiddleware, paramConverter, boardActionController); //CSRF for mod actions
+async function boardActionController(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')
+ //make sure they checked 1-10 posts
+ if (!req.body.checkedposts || req.body.checkedposts.length === 0 || req.body.checkedposts.length > 10) {
+ errors.push('Must select 1-10 posts');
+ }
+
+ res.locals.actions = actionChecker(req);
+
+ //make sure they selected at least 1 action
+ if (!res.locals.actions.anyValid) {
+ errors.push('No actions selected');
+ }
+ //check if they have permission to perform the actions
+ res.locals.hasPerms = checkPerms(req, res);
+ if(!res.locals.hasPerms && res.locals.actions.anyAuthed) {
+ errors.push('No permission');
+ }
+
+ //check that actions are valid
+ if (req.body.password && req.body.password.length > 50) {
+ errors.push('Password must be 50 characters or less');
+ }
+ if (req.body.report_reason && req.body.report_reason.length > 50) {
+ errors.push('Report must be 50 characters or less');
+ }
+ if (req.body.ban_reason && req.body.ban_reason.length > 50) {
+ errors.push('Ban reason must be 50 characters or less');
+ }
+ if ((req.body.report || req.body.global_report) && (!req.body.report_reason || req.body.report_reason.length === 0)) {
+ errors.push('Reports must have a reason');
}
if (errors.length > 0) {
return res.status(400).render('message', {
'title': 'Bad request',
'errors': errors,
- 'redirect': `/${req.params.board}/manage.html`
- });
+ 'redirect': `/${req.params.board}/`
+ })
+ }
+
+ res.locals.posts = await Posts.getPosts(req.params.board, req.body.checkedposts, true);
+ if (!res.locals.posts || res.locals.posts.length === 0) {
+ return res.status(404).render('message', {
+ 'title': 'Not found',
+ 'error': 'Selected posts not found',
+ 'redirect': `/${req.params.board}/`
+ })
}
- const messages = [];
try {
- messages.push((await removeBans(req, res, next)));
+ await actionHandler(req, res, next);
} catch (err) {
+ console.error(err);
return next(err);
}
- return res.render('message', {
- 'title': 'Success',
- 'messages': messages,
- 'redirect': `/${req.params.board}/manage.html`
- });
-
-});
+}
-router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async(req, res, next) => {
+//global actions (global manage page)
+router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, globalActionController);
+async function globalActionController(req, res, next) {
const errors = [];
@@ -392,10 +431,10 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
errors.push('Must select 1-10 posts')
}
- const { anyGlobal } = actionChecker(req);
+ res.locals.actions = actionChecker(req);
- //make sure they selected at least 1 global action
- if (!anyGlobal) {
+ //make sure they have any global actions, and that they only selected global actions
+ if (!res.locals.actions.anyGlobal || res.locals.actions.anyValid > res.locals.actions.anyGlobal) {
errors.push('Invalid actions selected');
}
@@ -403,15 +442,9 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
if (req.body.password && req.body.password.length > 50) {
errors.push('Password must be 50 characters or less');
}
- if (req.body.report_reason && req.body.report_reason.length > 50) {
- errors.push('Report must be 50 characters or less');
- }
if (req.body.ban_reason && req.body.ban_reason.length > 50) {
errors.push('Ban reason must be 50 characters or less');
}
- if (req.body.report && (!req.body.report_reason || req.body.report_reason.length === 0)) {
- errors.push('Reports must have a reason')
- }
//return the errors
if (errors.length > 0) {
@@ -423,8 +456,8 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
}
//get posts with global ids only
- const posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true);
- if (!posts || posts.length === 0) {
+ res.locals.posts = await Posts.globalGetPosts(req.body.globalcheckedposts, true);
+ if (!res.locals.posts || res.locals.posts.length === 0) {
return res.status(404).render('message', {
'title': 'Not found',
'errors': 'Selected posts not found',
@@ -432,80 +465,36 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
})
}
- //get the ids
- const postMongoIds = posts.map(post => Mongo.ObjectId(post._id));
+ try {
+ await actionHandler(req, res, next);
+ } catch (err) {
+ console.error(err);
+ return next(err);
+ }
+
+}
+
+//unban
+router.post('/board/:board/unban', csrf, Boards.exists, checkPermsMiddleware, paramConverter, 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.html`
+ });
+ }
+
const messages = [];
- const combinedQuery = {};
- let aggregateNeeded = false;
try {
- if (req.body.global_ban) {
- const { message, action, query } = await banPoster(req, res, next, null, posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
- }
- if (req.body.delete_ip_global) {
- const deletePostIps = posts.map(x => x.ip);
- const deleteIpPosts = await Posts.db.find({
- 'ip': {
- '$in': deletePostIps
- }
- }).toArray();
- if (deleteIpPosts && deleteIpPosts.length > 0) {
- const { message } = await deletePosts(req, res, next, deleteIpPosts, null);
- messages.push(message);
- aggregateNeeded = true;
- }
- } else if (req.body.delete) {
- const { message } = await deletePosts(req, res, next, posts);
- messages.push(message);
- aggregateNeeded = true;
- } else {
- // if it was getting deleted, we cant do any of these
- if (req.body.delete_file) {
- const { message, action, query } = await deletePostsFiles(posts);
- if (action) {
- aggregateNeeded = true;
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
- } else if (req.body.spoiler) {
- const { message, action, query } = spoilerPosts(posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
- }
- if (req.body.global_dismiss) {
- const { message, action, query } = dismissGlobalReports(posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
- }
- }
- if (Object.keys(combinedQuery).length > 0) {
- await Posts.db.updateMany({
- '_id': {
- '$in': postMongoIds
- }
- }, combinedQuery);
- }
- if (aggregateNeeded) {
- const threadsToUpdate = [...new Set(posts.filter(post => post.thread !== null))];
- //recalculate and set correct aggregation numbers again
- await Promise.all(threadsToUpdate.map(async (post) => {
- const replyCounts = await Posts.getReplyCounts(post.board, post.thread);
- let replyposts = 0;
- let replyfiles = 0;
- if (replyCounts[0]) {
- replyposts = replyCounts[0].replyposts;
- replyfiles = replyCounts[0].replyfiles;
- }
- Posts.setReplyCounts(post.board, post.thread, replyposts, replyfiles);
- }));
- }
+ messages.push((await removeBans(req, res, next)));
} catch (err) {
return next(err);
}
@@ -513,7 +502,7 @@ router.post('/global/actions', csrf, checkPermsMiddleware, paramConverter, async
return res.render('message', {
'title': 'Success',
'messages': messages,
- 'redirect': '/globalmanage.html'
+ 'redirect': `/${req.params.board}/manage.html`
});
});
diff --git a/db/boards.js b/db/boards.js
index 0e225f46..e0ec5baf 100644
--- a/db/boards.js
+++ b/db/boards.js
@@ -5,7 +5,7 @@ const Mongo = require(__dirname+'/db.js')
module.exports = {
- db,
+ db: db.collection('boards'),
findOne: (name) => {
return db.collection('boards').findOne({ '_id': name });
diff --git a/db/posts.js b/db/posts.js
index 15b9f787..83d06d44 100644
--- a/db/posts.js
+++ b/db/posts.js
@@ -2,7 +2,6 @@
const Mongo = require(__dirname+'/db.js')
, Boards = require(__dirname+'/boards.js')
- , deletePostFiles = require(__dirname+'/../helpers/files/deletepostfiles.js')
, db = Mongo.client.db('jschan').collection('posts');
module.exports = {
@@ -272,7 +271,7 @@ module.exports = {
},
insertOne: async (board, data, thread) => {
- if (data.thread !== null && data.email !== 'sage' && !thread.saged) {
+ if (data.thread !== null) {
const filter = {
'postId': data.thread,
'board': board
@@ -355,14 +354,6 @@ module.exports = {
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);
diff --git a/gulp/res/css/style.css b/gulp/res/css/style.css
index 891b216f..39d6258f 100644
--- a/gulp/res/css/style.css
+++ b/gulp/res/css/style.css
@@ -19,6 +19,17 @@ body {
flex-direction: column;
}
+pre {
+ font-family: inherit;
+ margin: 1em 2em;
+ white-space: pre-wrap;
+}
+
+.replies {
+ font-size: smaller;
+ clear: both;
+}
+
.code {
text-align: left;
border-left: 10px solid #B7C5D9;
@@ -27,7 +38,6 @@ body {
font-family: monospace;
margin: 0.5em 0;
display: flex;
- clear: both;
overflow-x: auto;
white-space: pre;
}
@@ -61,6 +71,7 @@ body {
box-sizing: border-box;
padding: 10px;
width: max-content;
+ width: -moz-max-content;
}
a, a:visited {
@@ -87,22 +98,8 @@ object {
background: #d6daf0;
}
-.catalog-tile-button {
- width: 100%;
- line-height: 30px;
- float: left;
- background: #B7C5D9;
- text-decoration: none;
- color: black;
- margin-bottom: 5px;
- overflow: hidden;
-}
-
-.catalog-tile-content {
- padding: 5px;
-}
-
.catalog-tile {
+ padding: 5px;
margin: 2px;
text-align: center;
max-height: 300px;
@@ -120,15 +117,15 @@ object {
.catalog-thumb {
box-shadow: 0 0 3px black;
- min-width: 64px;
- min-height: 64px;
+ width: 64px;
+ height: 64px;
object-fit: cover;
}
.catalog {
display:flex;
align-items:flex-start;
- justify-content: space-evenly;
+ justify-content: center;
flex-flow: row wrap;
}
@@ -158,6 +155,13 @@ object {
font-weight: bold;
}
+.close {
+ text-decoration: none;
+ width: 1.75em;
+ justify-content: center;
+ font-weight: bolder;
+}
+
.reports {
background: #fca!important;
border-color: #c97!important;
@@ -178,14 +182,10 @@ object {
color: green;
}
-blockquote a, a:hover {
+pre a, a:hover {
color: #d00!important;
}
-blockquote {
- white-space: pre-wrap;
-}
-
.thread, .action-wrapper, .form-wrapper, .table-container {
display: flex;
flex-direction: column;
@@ -219,7 +219,7 @@ td, th {
align-items: center;
}
-.post-container, .pages, .toggle-summary {
+.post-container, .pages, .toggle-summary, .catalog-tile {
background: #D6DAF0;
border: 1px solid #B7C5D9;
}
@@ -260,6 +260,7 @@ td, th {
display: flex;
flex-flow: column wrap;
width: max-content;
+ width: -moz-max-content;
}
.toggle {
@@ -326,7 +327,8 @@ td, th {
}
.post-file-src {
- margin: 0 auto;
+ justify-content: center;
+ display: flex;
}
.file-thumb {
@@ -390,8 +392,8 @@ input textarea {
}
.post-container:target {
- background-color: #d6bad0;
- border-color: #ba9dbf;
+ background-color: #d6bad0 !important;
+ border: 1px solid #ba9dbf !important;
}
.post-container.op {
@@ -470,7 +472,7 @@ input textarea {
input[type="text"], input[type="submit"], input[type="password"], input[type="file"], textarea {
border: 1px solid #a9a9a9;
font-size: inherit;
- font-family: arial,helvetica,sans-serif;
+ font-family: arial, helvetica, sans-serif;
margin: 0;
flex-grow: 1;
border-radius: 0px;
@@ -484,6 +486,24 @@ input[type="file"] {
background: white;
}
+#postform {
+ display: none;
+ width: 400px;
+ max-width: calc(100% - 10px);
+ position: fixed;
+ top: 45px;
+ right: 5px;
+ background-color: #D6DAF0;
+ border: 1px solid #b7c5d9;
+ padding: 5px;
+ z-index: 1;
+ box-sizing: border-box;
+}
+
+#postform:target {
+ display: flex;
+}
+
.postform-row, .postform-col {
display: flex;
}
@@ -539,14 +559,14 @@ hr {
}
.form-post {
- width: 100%;
+ width: calc(100% - 10px)!important;
}
.form-login {
width: 100%;
}
- blockquote {
+ pre {
margin: 1em;
}
@@ -573,8 +593,8 @@ hr {
width: 100%;
}
- .post-info {
- background-color: #B7C5D9;
+ #postform {
+ top: 5px!important;
}
}
diff --git a/gulp/res/img/deleted.png b/gulp/res/img/deleted.png
new file mode 100644
index 00000000..848c79c0
Binary files /dev/null and b/gulp/res/img/deleted.png differ
diff --git a/helpers/actionchecker.js b/helpers/actionchecker.js
index 10ab2486..ab43e884 100644
--- a/helpers/actionchecker.js
+++ b/helpers/actionchecker.js
@@ -1,16 +1,17 @@
'use strict';
const actions = [
+ {name:'unlink_file', global:true, auth:false, passwords:true},
+ {name:'delete_file', global:true, auth:true, passwords:false},
+ {name:'spoiler', global:true, auth:false, passwords:true},
+ {name:'delete', global:true, auth:false, passwords:true},
{name:'lock', global:false, auth:true, passwords:false},
{name:'sticky', global:false, auth:true, passwords:false},
{name:'sage', global:false, auth:true, passwords:false},
{name:'report', global:false, auth:false, passwords:false},
{name:'global_report', global:false, auth:false, passwords:false},
- {name:'spoiler', global:true, auth:false, passwords:true},
- {name:'delete', global:true, auth:false, passwords:true},
{name:'delete_ip_board', global:false, auth:true, passwords:false},
{name:'delete_ip_global', global:true, auth:true, passwords:false},
- {name:'delete_file', global:true, auth:false, passwords:true},
{name:'dismiss', global:false, auth:true, passwords:false},
{name:'global_dismiss', global:true, auth:true, passwords:false},
{name:'ban', global:false, auth:true, passwords:false},
@@ -19,26 +20,24 @@ const actions = [
module.exports = (req, res) => {
- let anyGlobal = false
- , anyAuthed = false
- , anyPasswords = false
- , anyValid = false;
+ let anyGlobal = 0
+ , anyAuthed = 0
+ , anyPasswords = 0
+ , anyValid = 0;
for (let i = 0; i < actions.length; i++) {
const action = actions[i];
const bodyHasAction = req.body[action.name];
if (bodyHasAction) {
- if (!anyGlobal && action.global) {
- anyGlobal = true;
- }
- if (!anyAuthed && action.auth) {
- anyAuthed = true;
+ anyValid++;
+ if (action.global) {
+ anyGlobal++;
}
- if (!anyPasswords && action.passwords) {
- anyPasswords = true;
+ if (action.auth) {
+ anyAuthed++;
}
- if (!anyValid) {
- anyValid = true;
+ if (action.passwords) {
+ anyPasswords++;
}
}
if (anyGlobal && anyAuthed && anyValid) {
diff --git a/helpers/captchaverify.js b/helpers/captchaverify.js
index 21ee4de9..ec939409 100644
--- a/helpers/captchaverify.js
+++ b/helpers/captchaverify.js
@@ -8,7 +8,9 @@ const Captchas = require(__dirname+'/../db/captchas.js')
module.exports = async (req, res, next) => {
//skip captcha if disabled on board for posts only
- if (res.locals.board && req.path === `/board/${res.locals.board._id}/post` && !res.locals.board.settings.captcha) {
+ if (res.locals.board
+ && req.path === `/board/${res.locals.board._id}/post`
+ && !res.locals.board.settings.captcha) {
return next();
}
diff --git a/helpers/files/deletetempfiles.js b/helpers/files/deletetempfiles.js
new file mode 100644
index 00000000..6a383e10
--- /dev/null
+++ b/helpers/files/deletetempfiles.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const remove = require('fs-extra').remove;
+
+module.exports = async (req) => {
+
+ if (req.files != null) {
+ let files = [];
+ const keys = Object.keys(req.files);
+ for (let i = 0; i < keys.length; i++) {
+ const val = req.files[keys[i]];
+ if (Array.isArray(val)) {
+ files = files.concat(val);
+ } else {
+ files.push(val);
+ }
+ }
+ return Promise.all(files.map(async file => {
+ remove(file.tempFilePath);
+ }));
+ }
+
+}
diff --git a/helpers/files/format-size.js b/helpers/files/formatsize.js
similarity index 100%
rename from helpers/files/format-size.js
rename to helpers/files/formatsize.js
diff --git a/helpers/files/image-identify.js b/helpers/files/imageidentify.js
similarity index 72%
rename from helpers/files/image-identify.js
rename to helpers/files/imageidentify.js
index 709c118d..f42789b3 100644
--- a/helpers/files/image-identify.js
+++ b/helpers/files/imageidentify.js
@@ -2,10 +2,10 @@ const gm = require('@tohru/gm')
, configs = require(__dirname+'/../../configs/main.json')
, uploadDirectory = require(__dirname+'/../uploadDirectory.js');
-module.exports = (filename, folder) => {
+module.exports = (filename, folder, temp) => {
return new Promise((resolve, reject) => {
- gm(`${uploadDirectory}${folder}/${filename}`)
+ gm(temp === true ? filename : `${uploadDirectory}${folder}/${filename}`)
.identify(function (err, data) {
if (err) {
return reject(err);
diff --git a/helpers/files/image-thumbnail.js b/helpers/files/imagethumbnail.js
similarity index 100%
rename from helpers/files/image-thumbnail.js
rename to helpers/files/imagethumbnail.js
diff --git a/helpers/files/file-check-mime-types.js b/helpers/files/mimetypes.js
similarity index 95%
rename from helpers/files/file-check-mime-types.js
rename to helpers/files/mimetypes.js
index 2f1eedad..55aef447 100644
--- a/helpers/files/file-check-mime-types.js
+++ b/helpers/files/mimetypes.js
@@ -13,6 +13,7 @@ const animatedImageMimeTypes = new Set([
]);
const videoMimeTypes = new Set([
+ 'video/quicktime',
'video/mp4',
'video/webm',
]);
diff --git a/helpers/files/video-identify.js b/helpers/files/videoidentify.js
similarity index 65%
rename from helpers/files/video-identify.js
rename to helpers/files/videoidentify.js
index dd98a735..a4ebbe57 100644
--- a/helpers/files/video-identify.js
+++ b/helpers/files/videoidentify.js
@@ -2,10 +2,10 @@ const ffmpeg = require('fluent-ffmpeg')
, configs = require(__dirname+'/../../configs/main.json')
, uploadDirectory = require(__dirname+'/../uploadDirectory.js');
-module.exports = (filename) => {
+module.exports = (filename, folder, temp) => {
return new Promise((resolve, reject) => {
- ffmpeg.ffprobe(`${uploadDirectory}img/${filename}`, (err, metadata) => {
+ ffmpeg.ffprobe(temp === true ? filename : `${uploadDirectory}${folder}/${filename}`, (err, metadata) => {
if (err) {
return reject(err)
}
diff --git a/helpers/files/video-thumbnail.js b/helpers/files/videothumbnail.js
similarity index 68%
rename from helpers/files/video-thumbnail.js
rename to helpers/files/videothumbnail.js
index 28d12f84..c05e7fc9 100644
--- a/helpers/files/video-thumbnail.js
+++ b/helpers/files/videothumbnail.js
@@ -2,7 +2,7 @@ const ffmpeg = require('fluent-ffmpeg')
, configs = require(__dirname+'/../../configs/main.json')
, uploadDirectory = require(__dirname+'/../uploadDirectory.js');
-module.exports = (filename) => {
+module.exports = (filename, geometry) => {
return new Promise((resolve, reject) => {
ffmpeg(`${uploadDirectory}img/${filename}`)
@@ -14,7 +14,8 @@ module.exports = (filename) => {
count: 1,
filename: `thumb-${filename.split('.')[0]}.jpg`,
folder: `${uploadDirectory}img/`,
- size: '128x?'
+ size: geometry.width > geometry.height ? '128x?' : '?x128'
+ //keep aspect ratio, but also making sure taller/wider thumbs dont exceed 128 in either dimension
});
});
diff --git a/helpers/haspermsmiddleware.js b/helpers/haspermsmiddleware.js
index e928c663..8f15cbff 100644
--- a/helpers/haspermsmiddleware.js
+++ b/helpers/haspermsmiddleware.js
@@ -4,7 +4,8 @@ const hasPerms = require(__dirname+'/hasperms.js');
module.exports = async (req, res, next) => {
- if (!hasPerms(req, res)) {
+ res.locals.hasPerms = hasPerms(req, res);
+ if (!res.locals.hasPerms) {
return res.status(403).render('message', {
'title': 'Forbidden',
'message': 'You do not have permission to access this page',
diff --git a/helpers/id-contrast.js b/helpers/id-contrast.js
deleted file mode 100644
index 50232bbb..00000000
--- a/helpers/id-contrast.js
+++ /dev/null
@@ -1,8 +0,0 @@
-'use strict';
-
-module.exports = (hex) => {
- const r = parseInt(hex.substr(0,2), 16);
- const g = parseInt(hex.substr(2,2), 16);
- const b = parseInt(hex.substr(4,2), 16)
- return 0.375 * r + 0.5 * g + 0.125 * b;
-}
diff --git a/helpers/paramconverter.js b/helpers/paramconverter.js
index 6ff9035b..3f31c91b 100644
--- a/helpers/paramconverter.js
+++ b/helpers/paramconverter.js
@@ -2,6 +2,7 @@
const Mongo = require(__dirname+'/../db/db.js')
, allowedArrays = new Set(['checkedposts', 'globalcheckedposts', 'checkedbans', 'checkedbanners'])
+ , numberFields = ['reply_limit', 'max_files', 'thread_limit', 'thread', 'min_message_length'];
module.exports = (req, res, next) => {
@@ -25,48 +26,19 @@ module.exports = (req, res, next) => {
if (req.body.globalcheckedposts) {
req.body.globalcheckedposts = req.body.globalcheckedposts.map(Mongo.ObjectId)
}
-
- //thread in post form
if (req.params.id) {
req.params.id = +req.params.id;
}
- if (req.body.thread) {
- req.body.thread = +req.body.thread;
- }
-
- //page number
- if (req.query.p) {
- const num = parseInt(req.query.p);
- if (Number.isSafeInteger(num)) {
- req.query.p = num;
- } else {
- req.query.p = null;
- }
- }
- //board settings
- if (req.body.reply_limit != null) {
- const num = parseInt(req.body.reply_limit);
- if (Number.isSafeInteger(num)) {
- req.body.reply_limit = num;
- } else {
- req.body.reply_limit = null;
- }
- }
- if (req.body.max_files != null) {
- const num = parseInt(req.body.max_files);
- if (Number.isSafeInteger(num)) {
- req.body.max_files = num;
- } else {
- req.body.max_files = null;
- }
- }
- if (req.body.thread_limit != null) {
- const num = +parseInt(req.body.thread_limit);
- if (Number.isSafeInteger(num)) {
- req.body.thread_limit = num;
- } else {
- req.body.thread_limit = null;
+ for (let i = 0; i < numberFields.length; i++) {
+ const field = numberFields[i];
+ if (req.body[field]) {
+ const num = parseInt(req.body[field]);
+ if (Number.isSafeInteger(num)) {
+ req.body[field] = num;
+ } else {
+ req.body[field] = null;
+ }
}
}
diff --git a/helpers/quotes.js b/helpers/quotes.js
index 46fe8d46..fcd1fac9 100644
--- a/helpers/quotes.js
+++ b/helpers/quotes.js
@@ -11,14 +11,14 @@ module.exports = async (board, text) => {
const quotes = text.match(quoteRegex);
const crossQuotes = text.match(crossQuoteRegex);
if (!quotes && !crossQuotes) {
- return text;
+ return { quotedMessage: text, threadQuotes: [] };
}
//make query for db including crossquotes
const queryOrs = []
const crossQuoteMap = {};
if (quotes) {
- const quoteIds = quotes.map(q => +q.substring(2));
+ const quoteIds = [...new Set(quotes.map(q => +q.substring(2)))]; //only uniques
queryOrs.push({
'board': board,
'postId': {
@@ -58,7 +58,7 @@ module.exports = async (board, text) => {
const posts = await Posts.getPostsForQuotes(queryOrs);
//if none of the quotes were real, dont do a replace
if (posts.length === 0) {
- return text;
+ return { quotedMessage: text, threadQuotes: [] };
}
//turn the result into a map of postId => threadId/postId
for (let i = 0; i < posts.length; i++) {
@@ -71,11 +71,13 @@ module.exports = async (board, text) => {
}
//then replace the quotes with only ones that exist
+ const threadQuotes = [];
if (quotes && Object.keys(postThreadIdMap).length > 0) {
text = text.replace(quoteRegex, (match) => {
const quotenum = +match.substring(2);
if (postThreadIdMap[board] && postThreadIdMap[board][quotenum]) {
- return `>>${quotenum}`;
+ threadQuotes.push(quotenum)
+ return `>>${quotenum}${postThreadIdMap[board][quotenum] === quotenum ? ' (OP) ' : ''}`;
}
return match;
});
@@ -94,6 +96,6 @@ module.exports = async (board, text) => {
});
}
- return text;
+ return { quotedMessage: text, threadQuotes: [...new Set(threadQuotes)] };
}
diff --git a/models/forms/actionhandler.js b/models/forms/actionhandler.js
index d876ed0d..d8a7c41b 100644
--- a/models/forms/actionhandler.js
+++ b/models/forms/actionhandler.js
@@ -1,85 +1,30 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')
+ , Boards = require(__dirname+'/../../db/boards.js')
, Mongo = require(__dirname+'/../../db/db.js')
- , banPoster = require(__dirname+'/ban-poster.js')
- , deletePosts = require(__dirname+'/delete-post.js')
- , spoilerPosts = require(__dirname+'/spoiler-post.js')
+ , banPoster = require(__dirname+'/banposter.js')
+ , deletePosts = require(__dirname+'/deletepost.js')
+ , spoilerPosts = require(__dirname+'/spoilerpost.js')
, stickyPosts = require(__dirname+'/stickyposts.js')
, sagePosts = require(__dirname+'/sageposts.js')
, lockPosts = require(__dirname+'/lockposts.js')
, deletePostsFiles = require(__dirname+'/deletepostsfiles.js')
- , reportPosts = require(__dirname+'/report-post.js')
+ , reportPosts = require(__dirname+'/reportpost.js')
, globalReportPosts = require(__dirname+'/globalreportpost.js')
- , dismissReports = require(__dirname+'/dismiss-report.js')
+ , dismissReports = require(__dirname+'/dismissreport.js')
, dismissGlobalReports = require(__dirname+'/dismissglobalreport.js')
- , actionChecker = require(__dirname+'/../../helpers/actionchecker.js')
- , checkPerms = require(__dirname+'/../../helpers/hasperms.js')
- , remove = require('fs-extra').remove
- , uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, { buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../build.js');
module.exports = async (req, res, next) => {
-
- const errors = [];
-
- //make sure they checked 1-10 posts
- if (!req.body.checkedposts || req.body.checkedposts.length === 0 || req.body.checkedposts.length > 10) {
- errors.push('Must select 1-10 posts');
- }
-
- //get what type of actions
- const { anyPasswords, anyAuthed, anyValid } = actionChecker(req);
-
- //make sure they selected at least 1 action
- if (!anyValid) {
- errors.push('No actions selected');
- }
- //check if they have permission to perform the actions
- const hasPerms = checkPerms(req, res);
- if(!hasPerms && anyAuthed) {
- errors.push('No permission');
- }
-
- //check that actions are valid
- if (req.body.password && req.body.password.length > 50) {
- errors.push('Password must be 50 characters or less');
- }
- if (req.body.report_reason && req.body.report_reason.length > 50) {
- errors.push('Report must be 50 characters or less');
- }
- if (req.body.ban_reason && req.body.ban_reason.length > 50) {
- errors.push('Ban reason must be 50 characters or less');
- }
- if ((req.body.report || req.body.global_report) && (!req.body.report_reason || req.body.report_reason.length === 0)) {
- errors.push('Reports must have a reason');
- }
-
- if (errors.length > 0) {
- return res.status(400).render('message', {
- 'title': 'Bad request',
- 'errors': errors,
- 'redirect': `/${req.params.board}/`
- })
- }
-
- let 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',
- 'error': 'Selected posts not found',
- 'redirect': `/${req.params.board}/`
- })
- }
-
//get the ids
- const postMongoIds = posts.map(post => Mongo.ObjectId(post._id));
+ const postMongoIds = res.locals.posts.map(post => Mongo.ObjectId(post._id));
let passwordPostMongoIds = [];
let passwordPosts = [];
- if (!hasPerms && anyPasswords) {
+ if (!res.locals.hasPerms && res.locals.actions.anyPasswords) {
//just to avoid multiple filters and mapping, do it all here
- passwordPosts = posts.filter(post => {
+ passwordPosts = res.locals.posts.filter(post => {
if (post.password != null
&& post.password.length > 0
&& post.password == req.body.password) {
@@ -91,11 +36,11 @@ 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 ? req.params.board+'/' : 'globalmanage.html'}`
});
}
} else {
- passwordPosts = posts;
+ passwordPosts = res.locals.posts;
passwordPostMongoIds = postMongoIds;
}
@@ -104,24 +49,22 @@ module.exports = async (req, res, next) => {
const passwordCombinedQuery = {};
let aggregateNeeded = false;
try {
- if (hasPerms) {
- // if getting global banned, board ban doesnt matter
- if (req.body.global_ban) {
- const { message, action, query } = await banPoster(req, res, next, null, posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
- } else if (req.body.ban) {
- const { message, action, query } = await banPoster(req, res, next, req.params.board, posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
+ // if getting global banned, board ban doesnt matter
+ if (req.body.global_ban) {
+ const { message, action, query } = await banPoster(req, res, next, null, res.locals.posts);
+ if (action) {
+ combinedQuery[action] = { ...combinedQuery[action], ...query}
}
+ messages.push(message);
+ } else if (req.body.ban) {
+ const { message, action, query } = await banPoster(req, res, next, req.params.board, res.locals.posts);
+ if (action) {
+ combinedQuery[action] = { ...combinedQuery[action], ...query}
+ }
+ messages.push(message);
}
- if (hasPerms && (req.body.delete_ip_board || req.body.delete_ip_global)) {
- const deletePostIps = posts.map(x => x.ip);
+ if (req.body.delete_ip_board || req.body.delete_ip_global) {
+ const deletePostIps = res.locals.posts.map(x => x.ip);
let query = {
'ip': {
'$in': deletePostIps
@@ -131,7 +74,7 @@ module.exports = async (req, res, next) => {
query['board'] = req.params.board;
}
const deleteIpPosts = await Posts.db.find(query).toArray();
- posts = posts.concat(deleteIpPosts);
+ res.locals.posts = res.locals.posts.concat(deleteIpPosts);
if (deleteIpPosts && deleteIpPosts.length > 0) {
const { message } = await deletePosts(req, res, next, deleteIpPosts, req.params.board);
messages.push(message);
@@ -143,8 +86,8 @@ module.exports = async (req, res, next) => {
aggregateNeeded = true;
} else {
// if it was getting deleted, we cant do any of these
- if (req.body.delete_file) {
- const { message, action, query } = await deletePostsFiles(passwordPosts);
+ if (req.body.delete_file || req.body.unlink_file) {
+ const { message, action, query } = await deletePostsFiles(passwordPosts, req.body.unlink_file);
if (action) {
aggregateNeeded = true;
passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query}
@@ -157,39 +100,37 @@ module.exports = async (req, res, next) => {
}
messages.push(message);
}
- if (hasPerms) {
- //lock, sticky, sage
- if (req.body.sage) {
- const { message, action, query } = sagePosts(posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
+ //lock, sticky, sage
+ if (req.body.sage) {
+ const { message, action, query } = sagePosts(res.locals.posts);
+ if (action) {
+ combinedQuery[action] = { ...combinedQuery[action], ...query}
}
- if (req.body.lock) {
- const { message, action, query } = lockPosts(posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
+ messages.push(message);
+ }
+ if (req.body.lock) {
+ const { message, action, query } = lockPosts(res.locals.posts);
+ if (action) {
+ combinedQuery[action] = { ...combinedQuery[action], ...query}
}
- if (req.body.sticky) {
- const { message, action, query } = stickyPosts(posts);
- if (action) {
- combinedQuery[action] = { ...combinedQuery[action], ...query}
- }
- messages.push(message);
+ messages.push(message);
+ }
+ if (req.body.sticky) {
+ const { message, action, query } = stickyPosts(res.locals.posts);
+ if (action) {
+ combinedQuery[action] = { ...combinedQuery[action], ...query}
}
+ messages.push(message);
}
// cannot report and dismiss at same time
if (req.body.report) {
- const { message, action, query } = reportPosts(req, posts);
+ const { message, action, query } = reportPosts(req, res.locals.posts);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
- } else if (hasPerms && req.body.dismiss) {
- const { message, action, query } = dismissReports(posts);
+ } else if (req.body.dismiss) {
+ const { message, action, query } = dismissReports(res.locals.posts);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
@@ -197,13 +138,13 @@ module.exports = async (req, res, next) => {
}
// cannot report and dismiss at same time
if (req.body.global_report) {
- const { message, action, query } = globalReportPosts(req, posts);
+ const { message, action, query } = globalReportPosts(req, res.locals.posts);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
- } else if (hasPerms && req.body.global_dismiss) {
- const { message, action, query } = dismissGlobalReports(posts);
+ } else if (req.body.global_dismiss) {
+ const { message, action, query } = dismissGlobalReports(res.locals.posts);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
@@ -239,8 +180,8 @@ module.exports = async (req, res, next) => {
//get a map of boards to threads affected
const boardThreadMap = {};
const queryOrs = [];
- for (let i = 0; i < posts.length; i++) {
- const post = posts[i];
+ for (let i = 0; i < res.locals.posts.length; i++) {
+ const post = res.locals.posts[i];
if (!boardThreadMap[post.board]) {
boardThreadMap[post.board] = [];
}
@@ -263,7 +204,7 @@ module.exports = async (req, res, next) => {
}
//get only posts (so we can use them for thread ids
- const postThreadsToUpdate = posts.filter(post => post.thread !== null);
+ const postThreadsToUpdate = res.locals.posts.filter(post => post.thread !== null);
if (aggregateNeeded) {
//recalculate replies and image counts
await Promise.all(postThreadsToUpdate.map(async (post) => {
@@ -296,7 +237,7 @@ module.exports = async (req, res, next) => {
'$or': queryOrs
}).toArray();
//combine it with what we already had
- threadsEachBoard = threadsEachBoard.concat(posts.filter(post => post.thread === null))
+ threadsEachBoard = threadsEachBoard.concat(res.locals.posts.filter(post => post.thread === null))
//get the oldest and newest thread for each board to determine how to delete
const threadBounds = threadsEachBoard.reduce((acc, curr) => {
@@ -315,36 +256,45 @@ module.exports = async (req, res, next) => {
//now we need to delete outdated html
//TODO: not do this for reports, handle global actions & move to separate handler + optimize and test
const parallelPromises = []
- const boardsWithChanges = Object.keys(threadBounds);
- for (let i = 0; i < boardsWithChanges.length; i++) {
- const changeBoard = boardsWithChanges[i];
- const bounds = threadBounds[changeBoard];
+ const boardNames = Object.keys(threadBounds);
+ const buildBoards = {};
+ const multiBoards = await Boards.db.find({
+ '_id': {
+ '$in': boardNames
+ }
+ }).toArray();
+ multiBoards.forEach(board => {
+ buildBoards[board._id] = board;
+ })
+ for (let i = 0; i < boardNames.length; i++) {
+ const boardName = boardNames[i];
+ const bounds = threadBounds[boardName];
//always need to refresh catalog
- parallelPromises.push(buildCatalog(res.locals.board));
+ parallelPromises.push(buildCatalog(buildBoards[boardName]));
//rebuild impacted threads
- for (let j = 0; j < boardThreadMap[changeBoard].length; j++) {
- parallelPromises.push(buildThread(boardThreadMap[changeBoard][j], changeBoard));
+ for (let j = 0; j < boardThreadMap[boardName].length; j++) {
+ parallelPromises.push(buildThread(boardThreadMap[boardName][j], buildBoards[boardName]));
}
//refersh any pages affected
- const afterPages = Math.ceil((await Posts.getPages(changeBoard)) / 10);
- if (beforePages[changeBoard] && beforePages[changeBoard] !== afterPages) {
+ const afterPages = Math.ceil((await Posts.getPages(boardName)) / 10);
+ if (beforePages[boardName] && beforePages[boardName] !== afterPages) {
//amount of pages changed, rebuild all pages
- parallelPromises.push(buildBoardMultiple(res.locals.board, 1, afterPages));
+ parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, afterPages));
} else {
- const threadPageOldest = await Posts.getThreadPage(req.params.board, bounds.oldest);
- const threadPageNewest = await Posts.getThreadPage(req.params.board, bounds.newest);
+ const threadPageOldest = await Posts.getThreadPage(boardName, bounds.oldest);
+ const threadPageNewest = await Posts.getThreadPage(boardName, bounds.newest);
if (req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global) {
//rebuild current and older pages for deletes
- parallelPromises.push(buildBoardMultiple(res.locals.board, threadPageNewest, afterPages));
+ parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, afterPages));
} else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant
- //rebuild current and newer pages for stickies
- parallelPromises.push(buildBoardMultiple(res.locals.board, 1, threadPageOldest));
- } else if ((hasPerms && (req.body.lock || req.body.sage)) || req.body.spoiler) {
+ //rebuild current and newer pages
+ parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, threadPageOldest));
+ } else if (req.body.lock || req.body.sage || req.body.spoiler || req.body.ban || req.body.global_ban || req.body.unlink_file) {
//rebuild inbewteen pages for things that dont cause page/thread movement
//should rebuild only affected pages, but finding the page of all affected
- //threads could end up being slower/more resource intensive. this is simpler
- //but still avoids rebuilding _some_ pages unnecessarily
- parallelPromises.push(buildBoardMultiple(res.locals.board, threadPageNewest, threadPageOldest));
+ //threads could end up being slower/more resource intensive. this is simpler.
+ //it avoids rebuilding _some_ but not all pages unnecessarily
+ parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest));
}
}
}
@@ -357,7 +307,7 @@ module.exports = async (req, res, next) => {
return res.render('message', {
'title': 'Success',
'messages': messages,
- 'redirect': `/${req.params.board}/`
+ 'redirect': `/${req.params.board ? req.params.board+'/' : 'globalmanage.html'}`
});
}
diff --git a/models/forms/ban-poster.js b/models/forms/banposter.js
similarity index 100%
rename from models/forms/ban-poster.js
rename to models/forms/banposter.js
diff --git a/models/forms/deletebanners.js b/models/forms/deletebanners.js
index f8015408..2d4ba234 100644
--- a/models/forms/deletebanners.js
+++ b/models/forms/deletebanners.js
@@ -9,11 +9,9 @@ module.exports = async (req, res, next) => {
const redirect = `/${req.params.board}/manage.html`
await Promise.all(req.body.checkedbanners.map(async filename => {
- remove(`${uploadDirectory}banner/${filename}`);
+ remove(`${uploadDirectory}banner/${req.params.board}/${filename}`);
}));
- // i dont think there is a way to get the number of array items removed with $pullAll
- // so i cant return how many banners were deleted
await Boards.removeBanners(req.params.board, req.body.checkedbanners);
return res.render('message', {
diff --git a/models/forms/delete-post.js b/models/forms/deletepost.js
similarity index 91%
rename from models/forms/delete-post.js
rename to models/forms/deletepost.js
index eee05592..04755730 100644
--- a/models/forms/delete-post.js
+++ b/models/forms/deletepost.js
@@ -1,7 +1,6 @@
'use strict';
const uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
- , deletePostFiles = require(__dirname+'/../../helpers/files/deletepostfiles.js')
, remove = require('fs-extra').remove
, Mongo = require(__dirname+'/../../db/db.js')
, Posts = require(__dirname+'/../../db/posts.js');
@@ -39,7 +38,7 @@ module.exports = async (req, res, next, posts, board) => {
//combine them all into one array, there may be duplicates but it shouldnt matter
const allPosts = posts.concat(threadPosts)
- //delete posts from DB
+ //get all mongoids and delete posts from
const postMongoIds = allPosts.map(post => Mongo.ObjectId(post._id))
const deletedPosts = await Posts.deleteMany(postMongoIds).then(result => result.deletedCount);
@@ -49,11 +48,6 @@ module.exports = async (req, res, next, posts, board) => {
fileNames = fileNames.concat(post.files.map(x => x.filename))
})
- //delete post files
- if (fileNames.length > 0) {
- await deletePostFiles(fileNames);
- }
-
//hooray!
return { message:`Deleted ${threads.length} threads and ${deletedPosts-threads.length} posts` };
diff --git a/models/forms/deletepostsfiles.js b/models/forms/deletepostsfiles.js
index b1d80988..beeb509d 100644
--- a/models/forms/deletepostsfiles.js
+++ b/models/forms/deletepostsfiles.js
@@ -3,7 +3,7 @@
const remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
-module.exports = async (posts) => {
+module.exports = async (posts, unlinkOnly) => {
//get filenames from all the posts
let fileNames = [];
@@ -13,25 +13,31 @@ module.exports = async (posts) => {
if (fileNames.length === 0) {
return {
- message: 'No files to delete'
+ message: 'No files found'
}
}
- //delete all the files using the filenames
- await Promise.all(fileNames.map(async filename => {
- //dont question it.
- return Promise.all([
- remove(`${uploadDirectory}img/${filename}`),
- remove(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.png`)
- ])
- }));
+ if (unlinkOnly) {
+ return {
+ message:`Unlinked ${fileNames.length} file(s) across ${posts.length} post(s)`,
+ action:'$set',
+ query: {
+ 'files': []
+ }
+ };
+ } else {
+ //delete all the files using the filenames
+ await Promise.all(fileNames.map(async filename => {
+ //dont question it.
+ return Promise.all([
+ remove(`${uploadDirectory}img/${filename}`),
+ remove(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.jpg`)
+ ])
+ }));
+ return {
+ message:`Deleted ${fileNames.length} file(s) from server`,
+ };
+ }
- return {
- message:`Deleted ${fileNames.length} file(s) across ${posts.length} post(s)`,
- action:'$set',
- query: {
- 'files': []
- }
- };
}
diff --git a/models/forms/dismiss-report.js b/models/forms/dismissreport.js
similarity index 100%
rename from models/forms/dismiss-report.js
rename to models/forms/dismissreport.js
diff --git a/models/forms/make-post.js b/models/forms/makepost.js
similarity index 79%
rename from models/forms/make-post.js
rename to models/forms/makepost.js
index ca8e9d03..3aa9fde3 100644
--- a/models/forms/make-post.js
+++ b/models/forms/makepost.js
@@ -1,11 +1,10 @@
'use strict';
-const uuidv4 = require('uuid/v4')
- , path = require('path')
+const path = require('path')
, util = require('util')
, crypto = require('crypto')
, randomBytes = util.promisify(crypto.randomBytes)
- , remove = require('fs-extra').remove
+ , { remove, pathExists } = require('fs-extra')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, Posts = require(__dirname+'/../../db/posts.js')
, getTripCode = require(__dirname+'/../../helpers/tripcode.js')
@@ -13,7 +12,7 @@ const uuidv4 = require('uuid/v4')
, simpleMarkdown = require(__dirname+'/../../helpers/markdown.js')
, sanitize = require('sanitize-html')
, sanitizeOptions = {
- allowedTags: [ 'span', 'a', 'em', 'strong' ],
+ allowedTags: [ 'span', 'a', 'em', 'strong', 'small' ],
allowedAttributes: {
'a': [ 'href', 'class' ],
'span': [ 'class' ]
@@ -23,12 +22,13 @@ const uuidv4 = require('uuid/v4')
, permsCheck = require(__dirname+'/../../helpers/hasperms.js')
, imageUpload = require(__dirname+'/../../helpers/files/imageupload.js')
, videoUpload = require(__dirname+'/../../helpers/files/videoupload.js')
- , fileCheckMimeType = require(__dirname+'/../../helpers/files/file-check-mime-types.js')
- , imageThumbnail = require(__dirname+'/../../helpers/files/image-thumbnail.js')
- , imageIdentify = require(__dirname+'/../../helpers/files/image-identify.js')
- , videoThumbnail = require(__dirname+'/../../helpers/files/video-thumbnail.js')
- , videoIdentify = require(__dirname+'/../../helpers/files/video-identify.js')
- , formatSize = require(__dirname+'/../../helpers/files/format-size.js')
+ , fileCheckMimeType = require(__dirname+'/../../helpers/files/mimetypes.js')
+ , imageThumbnail = require(__dirname+'/../../helpers/files/imagethumbnail.js')
+ , imageIdentify = require(__dirname+'/../../helpers/files/imageidentify.js')
+ , videoThumbnail = require(__dirname+'/../../helpers/files/videothumbnail.js')
+ , videoIdentify = require(__dirname+'/../../helpers/files/videoidentify.js')
+ , formatSize = require(__dirname+'/../../helpers/files/formatsize.js')
+ , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js')
, { buildCatalog, buildThread, buildBoard, buildBoardMultiple } = require(__dirname+'/../../build.js');
module.exports = async (req, res, next, numFiles) => {
@@ -42,6 +42,7 @@ module.exports = async (req, res, next, numFiles) => {
if (req.body.thread) {
thread = await Posts.getPost(req.params.board, req.body.thread, true);
if (!thread || thread.thread != null) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': 'Thread does not exist.',
@@ -51,6 +52,7 @@ module.exports = async (req, res, next, numFiles) => {
salt = thread.salt;
redirect += `thread/${req.body.thread}.html`
if (thread.locked && !hasPerms) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': 'Thread Locked',
@@ -58,6 +60,7 @@ module.exports = async (req, res, next, numFiles) => {
});
}
if (thread.replyposts >= res.locals.board.settings.replyLimit) { //reply limit
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': 'Thread reached reply limit',
@@ -66,6 +69,7 @@ module.exports = async (req, res, next, numFiles) => {
}
}
if (numFiles > res.locals.board.settings.maxFiles) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Too many files. Max files per post is ${res.locals.board.settings.maxFiles}.`,
@@ -78,6 +82,7 @@ module.exports = async (req, res, next, numFiles) => {
// check all mime types befoer we try saving anything
for (let i = 0; i < numFiles; i++) {
if (!fileCheckMimeType(req.files.file[i].mimetype, {animatedImage: true, image: true, video: true})) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Invalid file type for ${req.files.file[i].name}. Mimetype ${req.files.file[i].mimetype} not allowed.`,
@@ -88,50 +93,59 @@ module.exports = async (req, res, next, numFiles) => {
// then upload, thumb, get metadata, etc.
for (let i = 0; i < numFiles; i++) {
const file = req.files.file[i];
- const uuid = uuidv4();
- const filename = uuid + path.extname(file.name);
+ const filename = file.sha256 + path.extname(file.name);
file.filename = filename; //for error to delete failed files
//get metadata
let processedFile = {
+ hash: file.sha256,
filename: filename,
originalFilename: file.name,
mimetype: file.mimetype,
size: file.size,
};
+ //check if already exists
+ const existsFull = await pathExists(`${uploadDirectory}img/${filename}`);
+ const existsThumb = await pathExists(`${uploadDirectory}img/thumb-${filename.split('.')[0]}.jpg`);
+
//handle video/image ffmpeg or graphicsmagick
const mainType = file.mimetype.split('/')[0];
switch (mainType) {
case 'image':
- await imageUpload(file, filename, 'img');
- const imageData = await imageIdentify(filename, 'img');
+ const imageData = await imageIdentify(req.files.file[i].tempFilePath, null, true);
processedFile.geometry = imageData.size // object with width and height pixels
processedFile.sizeString = formatSize(processedFile.size) // 123 Ki string
processedFile.geometryString = imageData.Geometry // 123 x 123 string
- if (fileCheckMimeType(file.mimetype, {image: true}) //always thumbnail gif/webp
+ processedFile.hasThumb = !(fileCheckMimeType(file.mimetype, {image: true})
&& processedFile.geometry.height <= 128
- && processedFile.geometry.width <= 128) {
- processedFile.hasThumb = false;
- } else {
- processedFile.hasThumb = true;
+ && processedFile.geometry.width <= 128);
+ if (!existsFull) {
+ await imageUpload(file, filename, 'img');
+ }
+ if (!existsThumb && processedFile.hasThumb) {
await imageThumbnail(filename);
}
break;
case 'video':
//video metadata
- await videoUpload(file, filename, 'img');
- const videoData = await videoIdentify(filename);
+ const videoData = await videoIdentify(req.files.file[i].tempFilePath, null, true);
+ videoData.streams = videoData.streams.filter(stream => stream.width != null); //filter to only video streams or something with a resolution
processedFile.duration = videoData.format.duration;
processedFile.durationString = new Date(videoData.format.duration*1000).toLocaleString('en-US', {hour12:false}).split(' ')[1].replace(/^00:/, '');
processedFile.geometry = {width: videoData.streams[0].coded_width, height: videoData.streams[0].coded_height} // object with width and height pixels
processedFile.sizeString = formatSize(processedFile.size) // 123 Ki string
processedFile.geometryString = `${processedFile.geometry.width}x${processedFile.geometry.height}` // 123 x 123 string
processedFile.hasThumb = true;
- await videoThumbnail(filename);
+ if (!existsFull) {
+ await videoUpload(file, filename, 'img');
+ }
+ if (!existsThumb) {
+ await videoThumbnail(filename, processedFile.geometry);
+ }
break;
default:
- return next(err);
+ throw new Error(`invalid file mime type: ${mainType}`); //throw so goes to error handler before next'ing
}
//delete the temp file
@@ -161,7 +175,7 @@ module.exports = async (req, res, next, numFiles) => {
salt = (await randomBytes(128)).toString('hex');
}
if (res.locals.board.settings.ids) {
- const fullUserIdHash = crypto.createHash('sha256').update(salt + ip + req.params.board).digest('hex');
+ const fullUserIdHash = crypto.createHash('sha256').update(salt + ip).digest('hex');
userId = fullUserIdHash.substring(fullUserIdHash.length-6);
}
@@ -180,7 +194,7 @@ module.exports = async (req, res, next, numFiles) => {
const groups = matches.groups;
//name
if (groups.name) {
- name = groups.name
+ name = groups.name;
}
//tripcode
if (groups.tripcode) {
@@ -189,16 +203,19 @@ module.exports = async (req, res, next, numFiles) => {
//capcode
if (groups.capcode && hasPerms) {
// TODO: add proper code for different capcodes
- capcode = groups.capcode;
+ capcode = `## ${groups.capcode}`;
}
}
}
//simple markdown and sanitize
let message = req.body.message;
+ let quotes = [];
if (message && message.length > 0) {
message = simpleMarkdown(req.params.board, req.body.thread, message);
- message = await linkQuotes(req.params.board, message);
+ const { quotedMessage, threadQuotes } = await linkQuotes(req.params.board, message);
+ message = quotedMessage;
+ quotes = threadQuotes;
message = sanitize(message, sanitizeOptions);
}
@@ -222,6 +239,7 @@ module.exports = async (req, res, next, numFiles) => {
files,
'reports': [],
'globalreports': [],
+ quotes
}
if (!req.body.thread) {
@@ -247,7 +265,7 @@ module.exports = async (req, res, next, numFiles) => {
if (data.thread) {
//refersh pages
const threadPage = await Posts.getThreadPage(req.params.board, thread);
- if (data.email === 'sage') {
+ if (data.email === 'sage' || thread.sage) {
//refresh the page that the thread is on
parallelPromises.push(buildBoard(res.locals.board, threadPage));
} else {
diff --git a/models/forms/report-post.js b/models/forms/reportpost.js
similarity index 100%
rename from models/forms/report-post.js
rename to models/forms/reportpost.js
diff --git a/models/forms/spoiler-post.js b/models/forms/spoilerpost.js
similarity index 100%
rename from models/forms/spoiler-post.js
rename to models/forms/spoilerpost.js
diff --git a/models/forms/uploadbanners.js b/models/forms/uploadbanners.js
index 980b31ba..47a76186 100644
--- a/models/forms/uploadbanners.js
+++ b/models/forms/uploadbanners.js
@@ -1,13 +1,12 @@
'use strict';
-const uuidv4 = require('uuid/v4')
- , path = require('path')
- , remove = require('fs-extra').remove
+const path = require('path')
+ , { remove, pathExists, ensureDir } = require('fs-extra')
, uploadDirectory = require(__dirname+'/../../helpers/uploadDirectory.js')
, imageUpload = require(__dirname+'/../../helpers/files/imageupload.js')
- , fileCheckMimeType = require(__dirname+'/../../helpers/files/file-check-mime-types.js')
- , deleteFailedFiles = require(__dirname+'/../../helpers/files/deletefailed.js')
- , imageIdentify = require(__dirname+'/../../helpers/files/image-identify.js')
+ , fileCheckMimeType = require(__dirname+'/../../helpers/files/mimetypes.js')
+ , imageIdentify = require(__dirname+'/../../helpers/files/imageidentify.js')
+ , deleteTempFiles = require(__dirname+'/../../helpers/files/deletetempfiles.js')
, Boards = require(__dirname+'/../../db/boards.js')
module.exports = async (req, res, next, numFiles) => {
@@ -17,6 +16,7 @@ module.exports = async (req, res, next, numFiles) => {
// check all mime types befoer we try saving anything
for (let i = 0; i < numFiles; i++) {
if (!fileCheckMimeType(req.files.file[i].mimetype, {image: true, animatedImage: true, video: false})) {
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Invalid file type for ${req.files.file[i].name}. Mimetype ${req.files.file[i].mimetype} not allowed.`,
@@ -26,42 +26,57 @@ module.exports = async (req, res, next, numFiles) => {
}
const filenames = [];
- // then upload
for (let i = 0; i < numFiles; i++) {
const file = req.files.file[i];
- const uuid = uuidv4();
- const filename = uuid + path.extname(file.name);
- file.filename = filename; //for error to delete failed files
+ const filename = file.sha256 + path.extname(file.name);
+ file.filename = filename;
+
+ //check if already exists
+ const exists = await pathExists(`${uploadDirectory}banner/${req.params.board}/${filename}`);
+
+ if (exists) {
+ await remove(file.tempFilePath);
+ continue;
+ }
+
+ //add to list after checking it doesnt already exist
filenames.push(filename);
- //upload it
- await imageUpload(file, filename, 'banner');
- const imageData = await imageIdentify(filename, 'banner');
- const geometry = imageData.size;
- await remove(file.tempFilePath);
+ //make directory if doesnt exist
+ await ensureDir(`${uploadDirectory}banner/${req.params.board}/`);
+
+ //get metadata from tempfile
+ const imageData = await imageIdentify(req.files.file[i].tempFilePath, null, true);
+ let geometry = imageData.size;
+ if (Array.isArray(geometry)) {
+ geometry = geometry[0];
+ }
//make sure its 300x100 banner
if (geometry.width !== 300 || geometry.height !== 100) {
- const fileNames = [];
- for (let i = 0; i < req.files.file.length; i++) {
- remove(req.files.file[i].tempFilePath).catch(e => console.error);
- fileNames.push(req.files.file[i].filename);
- }
- deleteFailedFiles(fileNames, 'banner').catch(e => console.error);
+ await deleteTempFiles(req).catch(e => console.error);
return res.status(400).render('message', {
'title': 'Bad request',
'message': `Invalid file ${file.name}. Banners must be 300x100.`,
'redirect': redirect
});
}
+
+ //then upload it
+ await imageUpload(file, filename, `banner/${req.params.board}`);
+
+ //and delete the temp file
+ await remove(file.tempFilePath);
+
}
await Boards.addBanners(req.params.board, filenames);
+//TODO: banners pages
// await buildBanners(res.locals.board);
return res.render('message', {
'title': 'Success',
- 'message': `Uploaded ${filenames.length} banners.`,
+ 'message': `Uploaded ${filenames.length} new banners.`,
'redirect': redirect
});
diff --git a/models/pages/banners.js b/models/pages/banners.js
index dbd6b309..33c36290 100644
--- a/models/pages/banners.js
+++ b/models/pages/banners.js
@@ -8,21 +8,20 @@ module.exports = async (req, res, next) => {
return next();
}
- // get all threads
- let board;
- try {
- board = await Boards.findOne(req.query.board);
- } catch (err) {
- return next(err);
- }
-
- if (!board) {
- return next();
- }
-
- if (board.banners.length > 0) {
- const randomBanner = board.banners[Math.floor(Math.random()*board.banners.length)];
- return res.redirect(`/banner/${randomBanner}`);
+ //agregate to get single random item from banners array
+ const board = await Boards.db.aggregate([
+ {
+ '$unwind': '$banners'
+ },
+ {
+ '$sample': {
+ 'size' : 1
+ }
+ }
+ ]).toArray().then(res => res[0]);
+
+ if (board && board.banners != null) {
+ return res.redirect(`/banner/${req.query.board}/${board.banners}`);
}
return res.redirect('/img/defaultbanner.png');
diff --git a/package-lock.json b/package-lock.json
index ab716328..7e7fcfe1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,8 +5,8 @@
"requires": true,
"dependencies": {
"@tohru/gm": {
- "version": "git+https://github.com/iCrawl/gm.git#70ade5ebee96db0e38d621eb8f9744e5eee159c7",
- "from": "git+https://github.com/iCrawl/gm.git",
+ "version": "git+https://github.com/fatchan/gm.git#5f0ec1a0a262be8e4ac3916886ee55576db2c026",
+ "from": "git+https://github.com/fatchan/gm.git",
"requires": {
"array-parallel": "^0.1.3",
"array-series": "^0.1.5",
@@ -43,18 +43,62 @@
"@types/babel-types": "*"
}
},
+ "@types/events": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+ "dev": true
+ },
+ "@types/glob": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+ "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+ "dev": true,
+ "requires": {
+ "@types/events": "*",
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "@types/minimatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "12.0.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.3.tgz",
+ "integrity": "sha512-zkOxCS/fA+3SsdA+9Yun0iANxzhQRiNwTvJSr6N95JhuJ/x27z9G2URx1Jpt3zYFfCGUXZGL5UDxt5eyLE7wgw==",
+ "dev": true
+ },
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"accepts": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
- "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
- "mime-types": "~2.1.18",
- "negotiator": "0.6.1"
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+ },
+ "mime-types": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "requires": {
+ "mime-db": "1.40.0"
+ }
+ }
}
},
"accord": {
@@ -396,7 +440,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"assign-symbols": {
"version": "1.0.0",
@@ -582,6 +627,14 @@
}
}
},
+ "basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
"bcrypt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.6.tgz",
@@ -608,20 +661,35 @@
"dev": true
},
"body-parser": {
- "version": "1.18.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
- "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
- "bytes": "3.0.0",
+ "bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
- "http-errors": "~1.6.3",
- "iconv-lite": "0.4.23",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
- "qs": "6.5.2",
- "raw-body": "2.3.3",
- "type-is": "~1.6.16"
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "dependencies": {
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+ }
}
},
"brace-expansion": {
@@ -717,9 +785,9 @@
}
},
"bytes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
- "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"cache-base": {
"version": "1.0.1",
@@ -783,9 +851,9 @@
}
},
"chokidar": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz",
- "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==",
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz",
+ "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==",
"dev": true,
"requires": {
"anymatch": "^2.0.0",
@@ -961,6 +1029,7 @@
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
"dev": true,
+ "optional": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -1093,9 +1162,12 @@
}
},
"content-disposition": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
- "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
},
"content-security-policy-builder": {
"version": "2.0.0",
@@ -1338,11 +1410,12 @@
}
},
"del": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/del/-/del-4.1.0.tgz",
- "integrity": "sha512-C4kvKNlYrwXhKxz97BuohF8YoGgQ23Xm9lvoHmgT7JaPGprSEjk3+XFled74Yt/x0ZABUHg2D67covzAPUKx5Q==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
+ "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
"dev": true,
"requires": {
+ "@types/glob": "^7.1.1",
"globby": "^6.1.0",
"is-path-cwd": "^2.0.0",
"is-path-in-cwd": "^2.0.0",
@@ -1355,7 +1428,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"delegates": {
"version": "1.0.0",
@@ -1564,9 +1638,9 @@
}
},
"es5-ext": {
- "version": "0.10.49",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz",
- "integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==",
+ "version": "0.10.50",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz",
+ "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==",
"dev": true,
"requires": {
"es6-iterator": "~2.0.3",
@@ -1677,51 +1751,56 @@
}
},
"expect-ct": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.1.1.tgz",
- "integrity": "sha512-ngXzTfoRGG7fYens3/RMb6yYoVLvLMfmsSllP/mZPxNHgFq41TmPSLF/nLY7fwoclI2vElvAmILFWGUYqdjfCg=="
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz",
+ "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g=="
},
"express": {
- "version": "4.16.4",
- "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
- "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+ "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
- "accepts": "~1.3.5",
+ "accepts": "~1.3.7",
"array-flatten": "1.1.1",
- "body-parser": "1.18.3",
- "content-disposition": "0.5.2",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
"content-type": "~1.0.4",
- "cookie": "0.3.1",
+ "cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "1.1.1",
+ "finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
- "parseurl": "~1.3.2",
+ "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.4",
- "qs": "6.5.2",
- "range-parser": "~1.2.0",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
- "send": "0.16.2",
- "serve-static": "1.13.2",
- "setprototypeof": "1.1.0",
- "statuses": "~1.4.0",
- "type-is": "~1.6.16",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"dependencies": {
- "statuses": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
- "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
+ "cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
}
}
},
@@ -1857,7 +1936,8 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"fancy-log": {
"version": "1.3.3",
@@ -1886,9 +1966,9 @@
"optional": true
},
"feature-policy": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.2.0.tgz",
- "integrity": "sha512-2hGrlv6efG4hscYVZeaYjpzpT6I2OZgYqE2yDUzeAcKj2D1SH0AsEzqJNXzdoglEddcIXQQYop3lD97XpG75Jw=="
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
+ "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ=="
},
"fill-range": {
"version": "4.0.0",
@@ -1914,24 +1994,17 @@
}
},
"finalhandler": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
- "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
- "parseurl": "~1.3.2",
- "statuses": "~1.4.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
"unpipe": "~1.0.0"
- },
- "dependencies": {
- "statuses": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
- "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
- }
}
},
"find-up": {
@@ -2069,9 +2142,9 @@
}
},
"frameguard": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.0.0.tgz",
- "integrity": "sha1-e8rUae57lukdEs6zlZx4I1qScuk="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz",
+ "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g=="
},
"fresh": {
"version": "0.5.2",
@@ -2117,9 +2190,9 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz",
- "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==",
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
+ "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
"dev": true,
"optional": true,
"requires": {
@@ -2136,7 +2209,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -2157,12 +2231,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2177,17 +2253,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -2304,7 +2383,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -2316,6 +2396,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -2330,6 +2411,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -2337,12 +2419,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -2361,6 +2445,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -2441,7 +2526,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -2453,6 +2539,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -2538,7 +2625,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -2574,6 +2662,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -2593,6 +2682,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -2636,12 +2726,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
- "dev": true
+ "dev": true,
+ "optional": true
}
}
},
@@ -2839,9 +2931,9 @@
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"gulp": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.1.tgz",
- "integrity": "sha512-yDVtVunxrAdsk7rIV/b7lVSBifPN1Eqe6wTjsESGrFcL+MEVzaaeNTkpUuGTUptloSOU+8oJm/lBJbgPV+tMAw==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
+ "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==",
"dev": true,
"requires": {
"glob-watcher": "^5.0.3",
@@ -3114,24 +3206,24 @@
}
},
"helmet": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.16.0.tgz",
- "integrity": "sha512-rsTKRogc5OYGlvSHuq5QsmOsOzF6uDoMqpfh+Np8r23+QxDq+SUx90Rf8HyIKQVl7H6NswZEwfcykinbAeZ6UQ==",
+ "version": "3.18.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.18.0.tgz",
+ "integrity": "sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA==",
"requires": {
"depd": "2.0.0",
"dns-prefetch-control": "0.1.0",
"dont-sniff-mimetype": "1.0.0",
- "expect-ct": "0.1.1",
- "feature-policy": "0.2.0",
- "frameguard": "3.0.0",
+ "expect-ct": "0.2.0",
+ "feature-policy": "0.3.0",
+ "frameguard": "3.1.0",
"helmet-crossdomain": "0.3.0",
"helmet-csp": "2.7.1",
"hide-powered-by": "1.0.0",
"hpkp": "2.0.0",
"hsts": "2.2.0",
"ienoopen": "1.1.0",
- "nocache": "2.0.0",
- "referrer-policy": "1.1.0",
+ "nocache": "2.1.0",
+ "referrer-policy": "1.2.0",
"x-xss-protection": "1.1.0"
},
"dependencies": {
@@ -3212,14 +3304,15 @@
}
},
"http-errors": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
- "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
- "setprototypeof": "1.1.0",
- "statuses": ">= 1.4.0 < 2"
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
}
},
"http-signature": {
@@ -3432,27 +3525,27 @@
}
},
"is-path-cwd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.0.0.tgz",
- "integrity": "sha512-m5dHHzpOXEiv18JEORttBO64UgTEypx99vCxQLjbBvGhOJxnTNglYoFXxwo6AbsQb79sqqycQEHv2hWkHZAijA==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.1.0.tgz",
+ "integrity": "sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==",
"dev": true
},
"is-path-in-cwd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.0.0.tgz",
- "integrity": "sha512-6Vz5Gc9s/sDA3JBVu0FzWufm8xaBsqy1zn8Q6gmvGP6nSDMw78aS4poBNeatWjaRpTpxxLn1WOndAiOlk+qY8A==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+ "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
"dev": true,
"requires": {
- "is-path-inside": "^1.0.0"
+ "is-path-inside": "^2.1.0"
}
},
"is-path-inside": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
- "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+ "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
"dev": true,
"requires": {
- "path-is-inside": "^1.0.1"
+ "path-is-inside": "^1.0.2"
}
},
"is-plain-object": {
@@ -3552,7 +3645,8 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"json-schema": {
"version": "0.2.3",
@@ -3953,17 +4047,23 @@
"mime": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
- "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
+ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+ "dev": true,
+ "optional": true
},
"mime-db": {
"version": "1.35.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
- "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
+ "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==",
+ "dev": true,
+ "optional": true
},
"mime-types": {
"version": "2.1.19",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
"integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
+ "dev": true,
+ "optional": true,
"requires": {
"mime-db": "~1.35.0"
}
@@ -4035,11 +4135,11 @@
}
},
"mongodb": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.3.tgz",
- "integrity": "sha512-jw8UyPsq4QleZ9z+t/pIVy3L++51vKdaJ2Q/XXeYxk/3cnKioAH8H6f5tkkDivrQL4PUgUOHe9uZzkpRFH1XtQ==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.6.tgz",
+ "integrity": "sha512-qnHc4tjEkHKemuzBq9R7ycYnhFE0Dlpt6+n6suoZp2DcDdqviQ+teloJU24fsOw/PLmr75yGk4mRx/YabjDQEQ==",
"requires": {
- "mongodb-core": "^3.2.3",
+ "mongodb-core": "3.2.6",
"safe-buffer": "^5.1.2"
},
"dependencies": {
@@ -4049,9 +4149,9 @@
"integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg=="
},
"mongodb-core": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.3.tgz",
- "integrity": "sha512-UyI0rmvPPkjOJV8XGWa9VCTq7R4hBVipimhnAXeSXnuAPjuTqbyfA5Ec9RcYJ1Hhu+ISnc8bJ1KfGZd4ZkYARQ==",
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.6.tgz",
+ "integrity": "sha512-i+XRVjur9D0ywGF7cFebOUnALnbvMHajdNhhl3TQuopW6QDE655G8CpPeERbqSqfa3rOKEUo08lENDIiBIuAvQ==",
"requires": {
"bson": "^1.1.1",
"require_optional": "^1.0.1",
@@ -4070,6 +4170,18 @@
"require_optional": "~1.0.0"
}
},
+ "morgan": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
+ "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
+ "requires": {
+ "basic-auth": "~2.0.0",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.0.1"
+ }
+ },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -4139,9 +4251,9 @@
}
},
"negotiator": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
- "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"next-tick": {
"version": "1.0.0",
@@ -4150,9 +4262,9 @@
"dev": true
},
"nocache": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz",
- "integrity": "sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA="
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
+ "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
},
"node-pre-gyp": {
"version": "0.12.0",
@@ -4193,9 +4305,9 @@
},
"dependencies": {
"resolve": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
- "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz",
+ "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
@@ -4623,9 +4735,9 @@
"dev": true
},
"postcss": {
- "version": "7.0.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
- "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
+ "version": "7.0.16",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.16.tgz",
+ "integrity": "sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
@@ -4839,7 +4951,9 @@
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+ "dev": true,
+ "optional": true
},
"random-bytes": {
"version": "1.0.0",
@@ -4847,19 +4961,29 @@
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"range-parser": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
- "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
- "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
- "bytes": "3.0.0",
- "http-errors": "1.6.3",
- "iconv-lite": "0.4.23",
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
"unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ }
}
},
"rc": {
@@ -4958,9 +5082,9 @@
}
},
"referrer-policy": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz",
- "integrity": "sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
+ "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA=="
},
"regenerator-runtime": {
"version": "0.11.1",
@@ -5167,9 +5291,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sanitize-html": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.0.tgz",
- "integrity": "sha512-BpxXkBoAG+uKCHjoXFmox6kCSYpnulABoGcZ/R3QyY9ndXbIM5S94eOr1IqnzTG8TnbmXaxWoDDzKC5eJv7fEQ==",
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz",
+ "integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==",
"requires": {
"chalk": "^2.4.1",
"htmlparser2": "^3.10.0",
@@ -5184,9 +5308,9 @@
}
},
"saslprep": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz",
- "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+ "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
"optional": true,
"requires": {
"sparse-bitfield": "^3.0.3"
@@ -5212,9 +5336,9 @@
}
},
"send": {
- "version": "0.16.2",
- "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
- "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
@@ -5223,30 +5347,35 @@
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
- "http-errors": "~1.6.2",
- "mime": "1.4.1",
- "ms": "2.0.0",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
"on-finished": "~2.3.0",
- "range-parser": "~1.2.0",
- "statuses": "~1.4.0"
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
},
"dependencies": {
- "statuses": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
- "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"serve-static": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
- "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
- "parseurl": "~1.3.2",
- "send": "0.16.2"
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
}
},
"set-blocking": {
@@ -5278,9 +5407,9 @@
}
},
"setprototypeof": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
- "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"signal-exit": {
"version": "3.0.2",
@@ -5815,15 +5944,31 @@
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "dev": true
+ "dev": true,
+ "optional": true
},
"type-is": {
- "version": "1.6.16",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
- "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
- "mime-types": "~2.1.18"
+ "mime-types": "~2.1.24"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+ },
+ "mime-types": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "requires": {
+ "mime-db": "1.40.0"
+ }
+ }
}
},
"typedarray": {
@@ -6039,9 +6184,9 @@
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"v8flags": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.2.tgz",
- "integrity": "sha512-MtivA7GF24yMPte9Rp/BWGCYQNaUj86zeYxV/x2RRJMKagImbbv3u8iJC57lNhWLPcGLJmHcHmFWkNsplbbLWw==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz",
+ "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==",
"dev": true,
"requires": {
"homedir-polyfill": "^1.0.1"
diff --git a/package.json b/package.json
index 67d04b20..2ffd0f0d 100644
--- a/package.json
+++ b/package.json
@@ -4,28 +4,29 @@
"description": "",
"main": "server.js",
"dependencies": {
- "@tohru/gm": "git+https://github.com/iCrawl/gm.git",
+ "@tohru/gm": "git+https://github.com/fatchan/gm.git",
"bcrypt": "^3.0.6",
- "body-parser": "^1.18.3",
+ "body-parser": "^1.19.0",
"connect-mongo": "^2.0.3",
"cookie-parser": "^1.4.4",
"csurf": "^1.10.0",
- "express": "^4.16.4",
+ "express": "^4.17.1",
"express-fileupload": "^1.1.4",
"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",
+ "helmet": "^3.18.0",
+ "mongodb": "^3.2.6",
+ "morgan": "^1.9.1",
"path": "^0.12.7",
"pug": "^2.0.3",
- "sanitize-html": "^1.20.0",
+ "sanitize-html": "^1.20.1",
"uuid": "^3.3.2"
},
"devDependencies": {
- "del": "^4.1.0",
- "gulp": "^4.0.1",
+ "del": "^4.1.1",
+ "gulp": "^4.0.2",
"gulp-clean-css": "^4.2.0",
"gulp-concat": "^2.6.1",
"gulp-less": "^4.0.1",
diff --git a/server.js b/server.js
index eb8f1412..f0e20568 100644
--- a/server.js
+++ b/server.js
@@ -3,41 +3,29 @@
process.on('uncaughtException', console.error);
process.on('unhandledRejection', console.error);
-const express = require('express')
- , session = require('express-session')
+const express = require('express')
+ , session = require('express-session')
, MongoStore = require('connect-mongo')(session)
- , path = require('path')
- , app = express()
- , helmet = require('helmet')
+ , path = require('path')
+ , app = express()
, bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, configs = require(__dirname+'/configs/main.json')
- , Mongo = require(__dirname+'/db/db.js')
- , upload = require('express-fileupload');
+ , Mongo = require(__dirname+'/db/db.js');
(async () => {
// let db connect
await Mongo.connect();
- // parse forms and allow file uploads
+ // parse forms
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
- app.use(upload({
- createParentPath: true,
- safeFileNames: true,
- preserveExtension: 4,
- limits: {
- fileSize: 10 * 1024 * 1024,
- files: 3
- },
- abortOnLimit: true,
- useTempFiles: true,
- tempFileDir: path.join(__dirname+'/tmp/')
- }));
+
+ //parse cookies
+ app.use(cookieParser());
// session store
- app.set('trust proxy', 1);
app.use(session({
secret: configs.sessionSecret,
store: new MongoStore({ db: Mongo.client.db('sessions') }),
@@ -49,10 +37,9 @@ const express = require('express')
sameSite: 'lax',
}
}));
- app.use(cookieParser());
- // csurf and helmet
- app.use(helmet());
+ //trust proxy for nginx
+ app.set('trust proxy', 1);
//referer header check
app.use((req, res, next) => {
@@ -97,11 +84,11 @@ const express = require('express')
// listen
const server = app.listen(configs.port, '127.0.0.1', () => {
- console.log(`Listening on port ${configs.port}`);
+ console.log(`listening on port ${configs.port}`);
//let PM2 know that this is ready (for graceful reloads)
if (typeof process.send === 'function') { //make sure we are a child process
- console.info('Sending ready signal to PM2')
+ console.info('sending ready signal to PM2')
process.send('ready');
}
@@ -109,18 +96,20 @@ const express = require('express')
process.on('SIGINT', () => {
- console.info('SIGINT signal received.')
+ console.info('SIGINT signal received')
// Stops the server from accepting new connections and finishes existing connections.
server.close((err) => {
// if error, log and exit with error (1 code)
+ console.info('closing http server')
if (err) {
console.error(err);
process.exit(1);
}
// close database connection
+ console.info('closing db connection')
Mongo.client.close();
// now close without error
diff --git a/views/includes/actionfooter.pug b/views/includes/actionfooter.pug
index 5e938d6a..54c3997c 100644
--- a/views/includes/actionfooter.pug
+++ b/views/includes/actionfooter.pug
@@ -6,11 +6,11 @@ details.toggle-label
input.post-check(type='checkbox', name='delete' value=1)
| Delete
label
- input.post-check(type='checkbox', name='delete_file' value=1)
- | Delete File Only
+ input.post-check(type='checkbox', name='unlink_file' value=1)
+ | Unlink Files
label
input.post-check(type='checkbox', name='spoiler' value=1)
- | Spoiler Images
+ | Spoiler Files
label
input#password(type='text', name='password', placeholder='post password' autocomplete='off')
label
@@ -29,6 +29,9 @@ details.toggle-label
label
input.post-check(type='checkbox', name='delete_ip_global' value=1)
| Delete from IP globally
+ label
+ input.post-check(type='checkbox', name='delete_file' value=1)
+ | Delete Files
label
input.post-check(type='checkbox', name='sticky' value=1)
| Sticky
diff --git a/views/includes/actionfooter_globalmanage.pug b/views/includes/actionfooter_globalmanage.pug
index 90d9b47a..3d70d36d 100644
--- a/views/includes/actionfooter_globalmanage.pug
+++ b/views/includes/actionfooter_globalmanage.pug
@@ -7,10 +7,10 @@ details.toggle-label
| Delete
label
input.post-check(type='checkbox', name='delete_file' value=1)
- | Delete File Only
+ | Delete Files
label
input.post-check(type='checkbox', name='spoiler' value=1)
- | Spoiler Images
+ | Spoiler Files
label
input.post-check(type='checkbox', name='delete_ip_global' value=1)
| Delete from IP globally
diff --git a/views/includes/actionfooter_manage.pug b/views/includes/actionfooter_manage.pug
index 636e708c..d36f9770 100644
--- a/views/includes/actionfooter_manage.pug
+++ b/views/includes/actionfooter_manage.pug
@@ -7,10 +7,10 @@ details.toggle-label
| Delete
label
input.post-check(type='checkbox', name='delete_file' value=1)
- | Delete File Only
+ | Delete Files
label
input.post-check(type='checkbox', name='spoiler' value=1)
- | Spoiler Images
+ | Spoiler Files
label
input.post-check(type='checkbox', name='global_report' value=1)
| Global Report
diff --git a/views/includes/footer.pug b/views/includes/footer.pug
index 25ab0523..2f1c38a3 100644
--- a/views/includes/footer.pug
+++ b/views/includes/footer.pug
@@ -1,2 +1,2 @@
.footer
- a(href='https://github.com/fatchan/jschan/') not lynxchan™
+ a(href='https://github.com/fatchan/jschan/') open source
diff --git a/views/includes/postform.pug b/views/includes/postform.pug
index 38107809..496f2f28 100644
--- a/views/includes/postform.pug
+++ b/views/includes/postform.pug
@@ -1,47 +1,46 @@
section.form-wrapper.flex-center
- details.toggle-label
- summary.toggle-summary Show Post Form
- form.form-post(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)
- unless board.settings.forceAnon
- section.postform-row
- .postform-label Name
- input#name(type='text', name='name', placeholder=board.defaultName autocomplete='off' maxlength='50')
+ a.toggle-summary(href='#postform') Show Post Form
+ form.form-post#postform(action=`/forms/board/${board._id}/post`, enctype='multipart/form-data', method='POST')
+ input(type='hidden' name='thread' value=thread != null ? thread.postId : null)
+ unless board.settings.forceAnon
+ section.postform-row
+ .postform-label Name
+ input#name(type='text', name='name', placeholder=board.defaultName autocomplete='off' maxlength='50')
+ a.close.postform-style.ml-1(href='#!') X
+ section.postform-row
+ .postform-label Subject
+ input#title(type='text', name='subject', autocomplete='off' maxlength='50')
+ section.postform-row
+ .postform-label Email
+ input#name(type='text', name='email', autocomplete='off' maxlength='50')
+ else
+ section.postform-row
+ .postform-label Sage
+ label.postform-style.ph-5
+ input#spoiler(type='checkbox', name='email', value='sage')
+ | Sage
+ a.close.postform-style.ml-1(href='#!') X
+ if !thread
section.postform-row
.postform-label Subject
input#title(type='text', name='subject', autocomplete='off' maxlength='50')
- section.postform-row
- .postform-label Email
- input#name(type='text', name='email', autocomplete='off' maxlength='50')
- else
- section.postform-row
- .postform-label Sage
- label.postform-style.ph-5
- input#spoiler(type='checkbox', name='email', value='sage')
- | Sage
- if !thread
- section.postform-row
- .postform-label Subject
- input#title(type='text', name='subject', autocomplete='off' maxlength='50')
+ section.postform-row
+ .postform-label Message
+ textarea#message(name='message', rows='5', autocomplete='off' maxlength='2000')
+ if board.settings.maxFiles !== 0
section.postform-row
- .postform-label Message
- textarea#message(name='message', rows='5', autocomplete='off' maxlength='2000')
- 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
+ .postform-label Files
+ input#file(type='file', name='file' 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')
+ if board.settings.captcha
section.postform-row
- .postform-label Password
- input#password(type='password', name='password', autocomplete='off' placeholder='password for deleting post later' maxlength='50')
- if board.settings.captcha
- section.postform-row
- .postform-label Captcha
- .postform-col
- 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=`New ${threads ? 'Thread' : 'Reply'}`)
- //.mode Posting mode: #{threads ? 'Thread' : 'Reply'}
+ .postform-label Captcha
+ .postform-col
+ 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=`New ${threads ? 'Thread' : 'Reply'}`)
diff --git a/views/mixins/catalogtile.pug b/views/mixins/catalogtile.pug
index 24155d6a..0c59d1a8 100644
--- a/views/mixins/catalogtile.pug
+++ b/views/mixins/catalogtile.pug
@@ -1,30 +1,28 @@
mixin catalogtile(board, post, truncate)
article(class='catalog-tile')
- 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=postURL)
- 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]}.jpg` width='64' height='64')
- header.post-info
- if post.sticky
- img(src='/img/sticky.svg' height='12')
- if post.saged
- img(src='/img/saged.svg' height='12')
- if post.locked
- img(src='/img/locked.svg' height='12')
+ if post.files.length > 0
+ .post-file-src
+ a(href=postURL)
+ 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]}.jpg` width='64' height='64')
+ header.post-info
+ if post.sticky
+ img(src='/img/sticky.svg' height='12')
|
- span: a(href=postURL) No.#{post.postId}
+ if post.saged
+ img(src='/img/saged.svg' height='12')
|
- span Replies: #{post.replyposts}
+ if post.locked
+ img(src='/img/locked.svg' height='12')
|
- span Images: #{post.replyfiles}
- if post.message
- br
- blockquote.no-m-p.post-message !{post.message}
+ span: a.no-decoration.post-subject(href=postURL) #{post.subject || 'No Subject'}
+ br
+ span Replies: #{post.replyposts}
+ |
+ span Files: #{post.replyfiles}
+ if post.message
+ br
+ pre.no-m-p.post-message !{post.message}
diff --git a/views/mixins/post.pug b/views/mixins/post.pug
index a8ae0fd3..f4ab7a13 100644
--- a/views/mixins/post.pug
+++ b/views/mixins/post.pug
@@ -10,11 +10,13 @@ mixin post(post, truncate, manage=false, globalmanage=false)
if !post.thread
if post.sticky
img(src='/img/sticky.svg' height='12')
+ |
if post.saged
img(src='/img/saged.svg' height='12')
+ |
if post.locked
img(src='/img/locked.svg' height='12')
- |
+ |
if post.subject
span.post-subject #{post.subject}
|
@@ -36,13 +38,16 @@ mixin post(post, truncate, manage=false, globalmanage=false)
span.user-id(style=`background: #${post.userId}`) #{post.userId}
|
span: a(href=postURL) No.#{post.postId}
+ if !post.thread
+ |
+ span: a(href=`/${post.board}/thread/${post.thread || post.postId}.html#postform`) [Reply]
.post-data
if post.files.length > 0
.post-files
each file in post.files
.post-file
span.post-file-info
- span: a(href='/img/'+file.filename title=file.originalFilename download=file.originalFilename) #{file.originalFilename}
+ span: a(href='/img/'+file.filename title=file.originalFilename download=file.originalFilename) #{post.spoiler ? 'Spoiler File' : file.originalFilename}
br
span
| (#{file.sizeString}, #{file.geometryString}
@@ -55,8 +60,10 @@ mixin post(post, truncate, manage=false, globalmanage=false)
object.file-thumb(data='/img/spoiler.png' width='128' height='128')
else if file.hasThumb
object.file-thumb(data=`/img/thumb-${file.filename.split('.')[0]}.jpg`)
+ img(src='/img/deleted.png')
else
object.file-thumb(data=`/img/${file.filename}`)
+ img(src='/img/deleted.png')
if post.message
if truncate
-
@@ -64,20 +71,27 @@ mixin post(post, truncate, manage=false, globalmanage=false)
const messageLines = splitPost.length;
let truncatedMessage = post.message;
let truncated = false;
- if (messageLines > 10 || post.message.length > 1000) {
+ if (messageLines > 10) {
truncatedMessage = splitPost.slice(0, 10).join('\n');
truncated = true;
+ } else if (post.message.length > 1000) {
+ truncatedMessage = `${post.message.substring(0,1000)}...`;
+ truncated = true;
}
- blockquote.post-message !{truncatedMessage}
+ pre.post-message !{truncatedMessage}
if truncated
- blockquote.left.clear-both Message too long. #[a(href=postURL) View the full text]
+ blockquote Message too long. #[a(href=postURL) View the full text]
else
- blockquote.post-message !{post.message}
+ pre.post-message !{post.message}
if post.banmessage
- blockquote.left.clear-both.banmessage USER WAS BANNED FOR THIS POST (#{post.banmessage})
+ blockquote.banmessage USER WAS BANNED FOR THIS POST (#{post.banmessage})
if post.omittedposts || post.omittedimages
- blockquote.left.clear-both #{post.omittedposts} post(s)#{post.omittedimages > 0 ? ' and '+post.omittedimages+' image(s)' : ''} omitted. #[a(href=postURL) View the full thread]
-
+ blockquote #{post.omittedposts} post(s)#{post.omittedimages > 0 ? ' and '+post.omittedimages+' image(s)' : ''} omitted. #[a(href=postURL) View the full thread]
+ if post.backlinks && post.backlinks.length > 0
+ .replies Replies:
+ each backlink in post.backlinks
+ a.quote(href=`/${post.board}/thread/${post.thread || post.postId}.html#${backlink}`) >>#{backlink}
+ |
if manage === true
each report in post.reports
.reports.post-container
diff --git a/views/pages/catalog.pug b/views/pages/catalog.pug
index 945732bb..be09ee56 100644
--- a/views/pages/catalog.pug
+++ b/views/pages/catalog.pug
@@ -7,6 +7,8 @@ block head
block content
include ../includes/boardheader.pug
br
+ include ../includes/postform.pug
+ br
nav.pages#top
a(href='#bottom') [Bottom]
|
diff --git a/views/pages/manage.pug b/views/pages/manage.pug
index 43417353..116a3b97 100644
--- a/views/pages/manage.pug
+++ b/views/pages/manage.pug
@@ -72,7 +72,7 @@ block content
each banner in board.banners
label.banner-check
input(type='checkbox' name='checkedbanners[]' value=banner)
- object.board-banner(data=`/banner/${banner}` width='300' height='100')
+ object.board-banner(data=`/banner/${board._id}/${banner}` width='300' height='100')
input(type='submit', value='delete')
hr(size=1)
h4.no-m-p Reports:
diff --git a/views/pages/thread.pug b/views/pages/thread.pug
index 035c26c3..998ed669 100644
--- a/views/pages/thread.pug
+++ b/views/pages/thread.pug
@@ -22,7 +22,6 @@ block content
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)
section.thread
+post(thread)
for post in thread.replies
diff --git a/wipe.js b/wipe.js
index e35fe808..2037bdb2 100644
--- a/wipe.js
+++ b/wipe.js
@@ -42,7 +42,7 @@ const Mongo = require(__dirname+'/db/db.js')
moderators: [],
banners: [],
settings: {
- captcha: false,
+ captcha: true,
forceAnon: true,
ids: true,
threadLimit: 100,
@@ -63,7 +63,7 @@ const Mongo = require(__dirname+'/db/db.js')
moderators: [],
banners: [],
settings: {
- captcha: true,
+ captcha: false,
forceAnon: false,
ids: false,
threadLimit: 100,