Add modlogs (#48)

* public mod logs, per day and list of log days

* action handler variable names and logic changes, also dont duplicate modloglist code
merge-requests/208/head
Tom 5 years ago committed by GitHub
parent 0a64859358
commit f0ca405236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      controllers/forms/actions.js
  2. 2
      controllers/forms/deleteboard.js
  3. 4
      controllers/forms/globalactions.js
  4. 8
      controllers/pages.js
  5. 2
      db/bans.js
  6. 3
      db/index.js
  7. 48
      db/modlogs.js
  8. 8
      gulp/res/css/style.css
  9. 51
      helpers/build.js
  10. 3
      helpers/captcha/captchaverify.js
  11. 22
      helpers/checks/actionchecker.js
  12. 2
      helpers/checks/bancheck.js
  13. 2
      helpers/checks/spamcheck.js
  14. 12
      helpers/datearray.js
  15. 8
      helpers/paramconverter.js
  16. 112
      models/forms/actionhandler.js
  17. 13
      models/forms/deleteboard.js
  18. 2
      models/forms/removebans.js
  19. 30
      models/pages/modlog.js
  20. 18
      models/pages/modloglist.js
  21. 30
      views/includes/actionfooter.pug
  22. 14
      views/includes/actionfooter_globalmanage.pug
  23. 20
      views/includes/actionfooter_manage.pug
  24. 2
      views/includes/bannersheader.pug
  25. 2
      views/includes/boardheader.pug
  26. 2
      views/includes/catalogheader.pug
  27. 4
      views/includes/logheader.pug
  28. 2
      views/includes/pages.pug
  29. 2
      views/mixins/catalogtile.pug
  30. 4
      views/pages/banners.pug
  31. 6
      views/pages/board.pug
  32. 4
      views/pages/catalog.pug
  33. 40
      views/pages/modlog.pug
  34. 40
      views/pages/modloglist.pug
  35. 8
      views/pages/thread.pug

@ -1,6 +1,6 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')
const { Posts } = require(__dirname+'/../../db/')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js');
@ -16,7 +16,7 @@ module.exports = async (req, res, next) => {
res.locals.actions = actionChecker(req);
//make sure they selected at least 1 action
if (!res.locals.actions.anyValid) {
if (res.locals.actions.validActions.length === 0) {
errors.push('No actions selected');
}

@ -1,6 +1,6 @@
'use strict';
const Boards = require(__dirname+'/../../db/boards.js')
const { Boards } = require(__dirname+'/../../db/')
, deleteBoard = require(__dirname+'/../../models/forms/deleteboard.js')
, boardUriRegex = require(__dirname+'/../../helpers/checks/boarduriregex.js')

@ -1,6 +1,6 @@
'use strict';
const Posts = require(__dirname+'/../../db/posts.js')
const { Posts } = require(__dirname+'/../../db/')
, actionHandler = require(__dirname+'/../../models/forms/actionhandler.js')
, actionChecker = require(__dirname+'/../../helpers/checks/actionchecker.js');
@ -16,7 +16,7 @@ module.exports = async (req, res, next) => {
res.locals.actions = actionChecker(req);
//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) {
if (es.locals.actions.numGlobal === 0 || res.locals.actions.validActions.length > res.locals.actions.numGlobal) {
errors.push('Invalid actions selected');
}

@ -24,7 +24,9 @@ const express = require('express')
, news = require(__dirname+'/../models/pages/news.js')
, captchaPage = require(__dirname+'/../models/pages/captchapage.js')
, captcha = require(__dirname+'/../models/pages/captcha.js')
, thread = require(__dirname+'/../models/pages/thread.js');
, thread = require(__dirname+'/../models/pages/thread.js')
, modlog = require(__dirname+'/../models/pages/modlog.js')
, modloglist = require(__dirname+'/../models/pages/modloglist.js');
//homepage with board list
router.get('/index.html', home);
@ -38,6 +40,10 @@ router.get('/:board/thread/:id(\\d+).html', Boards.exists, paramConverter, Posts
// board catalog page
router.get('/:board/catalog.html', Boards.exists, catalog);
// modlogs
router.get('/:board/logs.html', Boards.exists, modloglist);
router.get('/:board/logs/:date(\\d{2}-\\d{2}-\\d{4}).html', Boards.exists, paramConverter, modlog);
// random board banner
router.get('/randombanner', randombanner);

@ -48,7 +48,7 @@ module.exports = {
'_id': {
'$in': ids
}
})
});
},
deleteBoard: (board) => {

@ -9,6 +9,7 @@ module.exports = {
Captchas: require(__dirname+'/captchas.js'),
Files: require(__dirname+'/files.js'),
News: require(__dirname+'/news.js'),
Ratelimits: require(__dirname+'/ratelimits.js')
Ratelimits: require(__dirname+'/ratelimits.js'),
Modlogs: require(__dirname+'/modlogs.js'),
}

@ -0,0 +1,48 @@
'use strict';
const Mongo = require(__dirname+'/db.js')
, db = Mongo.client.db('jschan').collection('modlog');
module.exports = {
getFirst: (board) => {
return db.find({
'board': board._id
}).sort({_id:-1}).limit(1).toArray();
},
getLast: (board) => {
return db.find({
'board': board._id
}).sort({_id:1}).limit(1).toArray();
},
findBetweenDate: (board, start, end) => {
const startDate = Mongo.ObjectId.createFromTime(Math.floor(start.getTime()/1000));
const endDate = Mongo.ObjectId.createFromTime(Math.floor(end.getTime()/1000));
return db.find({
'_id': {
'$gte': startDate,
'$lte': endDate
},
'board': board._id
}).sort({
'_id': -1
}).toArray();
},
insertMany: (events) => {
return db.insertMany(events);
},
deleteBoard: (board) => {
return db.deleteMany({
'board': board
});
},
deleteAll: () => {
return db.deleteMany({});
},
}

@ -9,8 +9,6 @@
--transparent-darken: #00000010;
--highlighted-post-color: #d6bad0;
--highlighted-post-outline-color: #ba9dbf;
--report-color: #fca;
--report-outline-color: #c97;
--board-title: #af0a0f;
--hr: lightgray;
--font-color: black;
@ -37,8 +35,6 @@
--transparent-darken: #00000010;
--highlighted-post-color: #ff000010;
--highlighted-post-outline-color: #111;
--report-color: #fca;
--report-outline-color: #c97;
--board-title: #c5c8c6;
--hr: #282a2e;
--font-color: #c5c8c6;
@ -275,8 +271,8 @@ p {
.reports {
margin-top: 5px;
background: var(--report-color) !important;
border-color: var(--report-outline-color)!important;
background: var(--highlighted-post-color) !important;
border-color: var(--highlighted-post-outline-color)!important;
border-width: 1px 0;
border-style: solid none;
}

@ -2,10 +2,8 @@
const Mongo = require(__dirname+'/../db/db.js')
, msTime = require(__dirname+'/mstime.js')
, Posts = require(__dirname+'/../db/posts.js')
, Files = require(__dirname+'/../db/files.js')
, Boards = require(__dirname+'/../db/boards.js')
, News = require(__dirname+'/../db/news.js')
, dateArray = require(__dirname+'/datearray.js')
, { Posts, Files, Boards, News, Modlogs } = require(__dirname+'/../db/')
, render = require(__dirname+'/render.js');
module.exports = {
@ -111,6 +109,51 @@ module.exports = {
console.timeEnd(label);
},
buildModLog: async (board, startDate, endDate, logs) => {
if (!startDate || !endDate) {
startDate = new Date(); //this is being built by action handler so will always be current date
endDate = new Date(startDate.getTime());
startDate.setHours(0,0,0,0);
endDate.setHours(23,59,59,999);
}
const day = ('0'+startDate.getDate()).slice(-2);
const month = ('0'+(startDate.getMonth()+1)).slice(-2);
const year = startDate.getFullYear();
const label = `/${board._id}/logs/${month}-${day}-${year}.html`;
console.time(label);
if (!logs) {
logs = await Modlogs.findBetweenDate(board, startDate, endDate);
}
await render(label, 'modlog.pug', {
board,
logs,
startDate,
endDate
});
console.timeEnd(label);
},
buildModLogList: async (board) => {
const label = `/${board._id}/logs.html`;
console.time(label);
let dates = []
const [ firstLog, lastLog ] = await Promise.all([
Modlogs.getFirst(board),
Modlogs.getLast(board)
]);
if (firstLog.length > 0 && lastLog.length > 0) {
const firstLogDate = firstLog[0].date;
firstLogDate.setHours(1,0,0,0);
const lastLogDate = lastLog[0].date;
dates = dateArray(firstLogDate, lastLogDate);
}
await render(label, 'modloglist.pug', {
board,
dates
});
console.timeEnd(label);
},
buildHomepage: async () => {
const label = '/index.html';
console.time(label);

@ -1,7 +1,6 @@
'use strict';
const Captchas = require(__dirname+'/../../db/captchas.js')
, Ratelimits = require(__dirname+'/../../db/ratelimits.js')
const { Captchas, Ratelimits } = require(__dirname+'/../../db/')
, { ObjectId } = require(__dirname+'/../../db/db.js')
, remove = require('fs-extra').remove
, uploadDirectory = require(__dirname+'/../files/uploadDirectory.js');

@ -13,40 +13,40 @@ const actions = [
{name:'global_report', global:true, auth:4, passwords:false, build:false},
{name:'delete_ip_board', global:true, auth:3, passwords:false, build:true},
{name:'delete_ip_global', global:true, auth:1, passwords:false, build:true},
{name:'dismiss', global:false, auth:3, passwords:false, build:false},
{name:'global_dismiss', global:true, auth:1, passwords:false, build:false},
{name:'dismiss', global:false, auth:3, passwords:false, build:true},
{name:'global_dismiss', global:true, auth:1, passwords:false, build:true},
{name:'ban', global:false, auth:3, passwords:false, build:true},
{name:'global_ban', global:true, auth:1, passwords:false, build:true},
];
module.exports = (req, res) => {
let anyGlobal = 0
let numGlobal = 0
, authRequired = 4
, anyPasswords = 0
, anyBuild = 0
, anyValid = 0;
, numPasswords = 0
, numBuild = 0
, validActions = [];
for (let i = 0; i < actions.length; i++) {
const action = actions[i];
const bodyHasAction = req.body[action.name];
if (bodyHasAction) {
anyValid++;
validActions.push(action.name);
if (action.global) {
anyGlobal++;
numGlobal++;
}
if (action.auth && action.auth < authRequired) {
authRequired = action.auth;
}
if (action.passwords) {
anyPasswords++;
numPasswords++;
}
if (action.build) {
anyBuild++
numBuild++
}
}
}
return { anyGlobal, authRequired, anyValid, anyPasswords, anyBuild };
return { numGlobal, authRequired, validActions, numPasswords, numBuild };
}

@ -1,6 +1,6 @@
'use strict';
const Bans = require(__dirname+'/../../db/bans.js')
const { Bans } = require(__dirname+'/../../db/')
, hasPerms = require(__dirname+'/hasperms.js');
module.exports = async (req, res, next) => {

@ -1,7 +1,7 @@
'use strict';
const Mongo = require(__dirname+'/../../db/db.js')
, Posts = require(__dirname+'/../../db/posts.js')
, { Posts } = require(__dirname+'/../../db/')
, msTime = require(__dirname+'/../mstime.js')
module.exports = async (req, res) => {

@ -0,0 +1,12 @@
'use strict';
//https://stackoverflow.com/a/4413721
module.exports = (startDate, stopDate) => {
var dateArray = new Array();
var currentDate = startDate;
while (currentDate <= stopDate) {
dateArray.push(new Date (currentDate.valueOf()));
currentDate.setDate(currentDate.getDate() + 1);
}
return dateArray;
}

@ -92,6 +92,14 @@ module.exports = (req, res, next) => {
if (req.params.id) {
req.params.id = +req.params.id;
}
//moglog date
if (req.params.date) {
const dateString = req.params.date.replace(/-/g, '/');
const date = new Date(dateString);
if (date !== 'Invalid Date') {
res.locals.date = date;
}
}
//board page
if (req.params.page) {
req.params.page = req.params.page === 'index' ? 'index' : +req.params.page;

@ -1,6 +1,6 @@
'use strict';
const { Posts, Boards } = require(__dirname+'/../../db/')
const { Posts, Boards, Modlogs } = require(__dirname+'/../../db/')
, Mongo = require(__dirname+'/../../db/db.js')
, banPoster = require(__dirname+'/banposter.js')
, deletePosts = require(__dirname+'/deletepost.js')
@ -16,7 +16,7 @@ const { Posts, Boards } = require(__dirname+'/../../db/')
, dismissGlobalReports = require(__dirname+'/dismissglobalreport.js')
, { remove } = require('fs-extra')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
, { buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../helpers/build.js')
, { buildModLog, buildModLogList, buildCatalog, buildThread, buildBoardMultiple } = require(__dirname+'/../../helpers/build.js')
, { postPasswordSecret } = require(__dirname+'/../../configs/main.json')
, { createHash, timingSafeEqual } = require('crypto');
@ -26,7 +26,7 @@ module.exports = async (req, res, next) => {
const postMongoIds = res.locals.posts.map(post => Mongo.ObjectId(post._id));
let passwordPostMongoIds = [];
let passwordPosts = [];
if (res.locals.authLevel >= 4 && res.locals.actions.anyPasswords) {
if (res.locals.permLevel >= 4 && res.locals.actions.numPasswords > 0) {
if (req.body.password && req.body.password.length > 0) {
//hash their input and make it a buffer
const inputPasswordHash = createHash('sha256').update(postPasswordSecret + req.body.password).digest('base64');
@ -34,7 +34,7 @@ module.exports = async (req, res, next) => {
passwordPosts = res.locals.posts.filter(post => {
if (post.password != null) {
const postBuffer = Buffer.from(post.password);
if (timingSafeEqual(inputBuffer, postBuffer) === true) {
if (timingSafeEqual(inputPasswordBuffer, postBuffer) === true) {
passwordPostMongoIds.push(Mongo.ObjectId(post._id));
return true;
}
@ -83,20 +83,16 @@ module.exports = async (req, res, next) => {
}
const messages = [];
const modlogActions = []
const combinedQuery = {};
const passwordCombinedQuery = {};
let aggregateNeeded = false;
try {
// if getting global banned, board ban doesnt matter
if (req.body.global_ban) {
const { message, action, query } = await banPoster(req, res, next);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
} else if (req.body.ban) {
if (req.body.ban || req.body.global_ban) {
const { message, action, query } = await banPoster(req, res, next);
if (action) {
modlogActions.push(req.body.ban || req.body.global_ban);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -122,6 +118,7 @@ module.exports = async (req, res, next) => {
const { action, message } = await deletePosts(passwordPosts, req.body.delete_ip_global ? null : req.params.board);
messages.push(message);
if (action) {
modlogActions.push(req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global);
aggregateNeeded = true;
}
} else {
@ -129,6 +126,7 @@ module.exports = async (req, res, next) => {
if (req.body.unlink_file || req.body.delete_file) {
const { message, action, query } = await deletePostsFiles(passwordPosts, req.body.unlink_file);
if (action) {
modlogActions.push(req.body.unlink_file || req.body.delete_file);
aggregateNeeded = true;
passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query}
}
@ -136,6 +134,7 @@ module.exports = async (req, res, next) => {
} else if (req.body.spoiler) {
const { message, action, query } = spoilerPosts(passwordPosts);
if (action) {
modlogActions.push(req.body.spoiler);
passwordCombinedQuery[action] = { ...passwordCombinedQuery[action], ...query}
}
messages.push(message);
@ -144,6 +143,7 @@ module.exports = async (req, res, next) => {
if (req.body.sage) {
const { message, action, query } = sagePosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.sage);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -151,22 +151,25 @@ module.exports = async (req, res, next) => {
if (req.body.lock) {
const { message, action, query } = lockPosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.lock);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
}
messages.push(message);
}
if (req.body.sticky) {
const { message, action, query } = stickyPosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.sticky);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
}
messages.push(message);
}
if (req.body.cyclic) {
const { message, action, query } = cyclePosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.cyclic);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
}
messages.push(message);
}
// cannot report and dismiss at same time
@ -174,13 +177,14 @@ module.exports = async (req, res, next) => {
const { message, action, query } = reportPosts(req, res, res.locals.posts);
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
}
messages.push(message);
} else if (req.body.dismiss) {
const { message, action, query } = dismissReports(res.locals.posts);
if (action) {
modlogActions.push(req.body.dismiss);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
}
messages.push(message);
}
// cannot report and dismiss at same time
@ -193,15 +197,15 @@ module.exports = async (req, res, next) => {
} else if (req.body.global_dismiss) {
const { message, action, query } = dismissGlobalReports(res.locals.posts);
if (action) {
modlogActions.push(req.body.global_dismiss);
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
}
messages.push(message);
}
}
//console.log(require('util').inspect(combinedQuery, {depth: null}))
const bulkWrites = [];
const actionBulkWrites = [];
if (Object.keys(combinedQuery).length > 0) {
bulkWrites.push({
actionBulkWrites.push({
'updateMany': {
'filter': {
'_id': {
@ -213,7 +217,7 @@ module.exports = async (req, res, next) => {
});
}
if (Object.keys(passwordCombinedQuery).length > 0) {
bulkWrites.push({
actionBulkWrites.push({
'updateMany': {
'filter': {
'_id': {
@ -224,14 +228,48 @@ module.exports = async (req, res, next) => {
}
});
}
//execute actions now
if (bulkWrites.length > 0) {
await Posts.db.bulkWrite(bulkWrites);
if (actionBulkWrites.length > 0) {
await Posts.db.bulkWrite(actionBulkWrites);
}
//if there are actions that can cause some rebuilding
if (res.locals.actions.anyBuild > 0) {
if (res.locals.actions.numBuild > 0) {
//modlog
if (modlogActions.length > 0) {
const modlog = {};
const logDate = new Date(); //all events current date
for (let i = 0; i < passwordPosts.length; i++) {
const post = passwordPosts[i];
if (!modlog[post.board]) {
//per board actions, all actions combined to one event
const logUser = res.locals.permLevel < 4 ? req.session.user.username : 'Unregistered User'
modlog[post.board] = {
postIds: [],
actions: modlogActions,
date: logDate,
user: logUser
};
}
//push each post id
modlog[post.board].postIds.push(post.postId);
}
const modlogDocuments = [];
for (let i = 0; i < threadBoards.length; i++) {
const boardName = threadBoards[i];
const boardLog = modlog[boardName];
//make it into documents for the db
modlogDocuments.push({
...boardLog,
'board': boardName
});
}
if (modlogDocuments.length > 0) {
//insert the modlog docs
await Modlogs.insertMany(modlogDocuments);
}
}
//recalculate replies and image counts
if (aggregateNeeded) {
@ -263,6 +301,7 @@ module.exports = async (req, res, next) => {
})
}
//fetch threads per board that we only checked posts for
let threadsEachBoard = await Posts.db.find({
'thread': null,
@ -302,9 +341,10 @@ module.exports = async (req, res, next) => {
for (let i = 0; i < boardNames.length; i++) {
const boardName = boardNames[i];
const bounds = threadBounds[boardName];
const board = buildBoards[boardName];
//rebuild impacted threads
for (let j = 0; j < boardThreadMap[boardName].threads.length; j++) {
parallelPromises.push(buildThread(boardThreadMap[boardName].threads[j], buildBoards[boardName]));
parallelPromises.push(buildThread(boardThreadMap[boardName].threads[j], board));
}
//refersh any pages affected
const afterPages = Math.ceil((await Posts.getPages(boardName)) / 10);
@ -318,7 +358,7 @@ module.exports = async (req, res, next) => {
parallelPromises.push(remove(`${uploadDirectory}html/${boardName}/${k}.html`));
}
}
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, afterPages));
parallelPromises.push(buildBoardMultiple(board, 1, afterPages));
} else {
//number of pages did not change, only possibly building existing pages
const threadPageOldest = await Posts.getThreadPage(boardName, bounds.oldest);
@ -326,18 +366,18 @@ module.exports = async (req, res, next) => {
if (req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global) {
if (!boardThreadMap[boardName].directThreads) {
//onyl deleting posts from threads, so thread order wont change, thus we dont delete all pages after
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest));
parallelPromises.push(buildBoardMultiple(board, threadPageNewest, threadPageOldest));
} else {
//deleting threads, so we delete all pages after
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, afterPages));
parallelPromises.push(buildBoardMultiple(board, threadPageNewest, afterPages));
}
} else if (req.body.sticky) { //else if -- if deleting, other actions are not executed/irrelevant
//rebuild current and newer pages
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], 1, threadPageOldest));
parallelPromises.push(buildBoardMultiple(board, 1, threadPageOldest));
} else if (req.body.lock || req.body.sage || req.body.cyclic || req.body.unlink_file) {
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest));
parallelPromises.push(buildBoardMultiple(board, threadPageNewest, threadPageOldest));
} else if (req.body.spoiler || req.body.ban || req.body.global_ban) {
parallelPromises.push(buildBoardMultiple(buildBoards[boardName], threadPageNewest, threadPageOldest));
parallelPromises.push(buildBoardMultiple(board, threadPageNewest, threadPageOldest));
if (!boardThreadMap[boardName].directThreads) {
catalogRebuild = false;
//these actions dont affect the catalog tile since not on an OP and dont change reply/image counts
@ -346,7 +386,13 @@ module.exports = async (req, res, next) => {
}
if (catalogRebuild) {
//the actions will affect the catalog, so we better rebuild it
parallelPromises.push(buildCatalog(buildBoards[boardName]));
parallelPromises.push(buildCatalog(board));
}
if (modlogActions.length > 0) {
//modlog had something added
parallelPromises.push(buildModLog(board));
parallelPromises.push(buildModLogList(board));
//todo: build loglist on a schedule. not necessary to build every time modlog changes, only daily/end of day
}
}
await Promise.all(parallelPromises);

@ -1,6 +1,6 @@
'use strict';
const { Boards, Posts, Bans } = require(__dirname+'/../../db/')
const { Boards, Posts, Bans, Modlogs } = require(__dirname+'/../../db/')
, deletePosts = require(__dirname+'/deletepost.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
, { remove } = require('fs-extra');
@ -15,11 +15,10 @@ module.exports = async (uri) => {
//delete posts and decrement images
await deletePosts(allPosts, uri, true);
}
//bans for the board are pointless now
await Bans.deleteBoard(uri);
//remove all pages for the board
await remove(`${uploadDirectory}html/${uri}/`)
//todo: maybe put some of this in parallel
await Promise.all([
Modlogs.deleteBoard(uri), //bans for the board
Bans.deleteBoard(uri), //bans for the board
remove(`${uploadDirectory}html/${uri}/`) //html
]);
}

@ -4,6 +4,6 @@ const { Bans } = require(__dirname+'/../../db/');
module.exports = async (req, res, next) => {
return Bans.removeMany(req.params.board, banIds).then(result => result.deletedCount);
return Bans.removeMany(req.params.board, req.body.checkedbans).then(result => result.deletedCount);
}

@ -0,0 +1,30 @@
'use strict';
const { Posts, Modlogs } = require(__dirname+'/../../db/')
, { buildModLog } = require(__dirname+'/../../helpers/build.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js');
module.exports = async (req, res, next) => {
if (!res.locals.date) {
return next();
}
const startDate = res.locals.date;
const endDate = new Date(startDate.getTime());
startDate.setHours(0,0,0,0);
endDate.setHours(23,59,59,999);
try {
const logs = await Modlogs.findBetweenDate(res.locals.board, startDate, endDate);
if (!logs || logs.length === 0) {
return next();
}
await buildModLog(res.locals.board, startDate, endDate, logs);
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html${req.path}`);
}

@ -0,0 +1,18 @@
'use strict';
const { Posts, Modlogs } = require(__dirname+'/../../db/')
, { buildModLogList } = require(__dirname+'/../../helpers/build.js')
, dateArray = require(__dirname+'/../../helpers/datearray.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js');
module.exports = async (req, res, next) => {
try {
await buildModLogList(res.locals.board);
} catch (err) {
return next(err);
}
return res.sendFile(`${uploadDirectory}html${req.path}`);
}

@ -3,59 +3,59 @@ details.toggle-label
.actions
h4.no-m-p Actions:
label
input.post-check(type='checkbox', name='delete' value=1)
input.post-check(type='checkbox', name='delete' value='Delete post')
| Delete Posts
label
input.post-check(type='checkbox', name='unlink_file' value=1)
input.post-check(type='checkbox', name='unlink_file' value='Unlink files')
| Unlink Files
label
input.post-check(type='checkbox', name='spoiler' value=1)
input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
| Spoiler Files
label
input#password(type='text', name='password', placeholder='post password' autocomplete='off')
label
input.post-check(type='checkbox', name='report' value=1)
input.post-check(type='checkbox', name='report' value='Report')
| Report
label
input.post-check(type='checkbox', name='global_report' value=1)
input.post-check(type='checkbox', name='global_report' value='Report global')
| Global Report
label
input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
details.actions
summary(style='font-weight: bold') Staff Actions:
label
input.post-check(type='checkbox', name='delete_ip_board' value=1)
input.post-check(type='checkbox', name='delete_ip_board' value='Delete posts by IP')
| Delete from IP on board
label
input.post-check(type='checkbox', name='delete_ip_global' value=1)
input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
| Delete from IP globally
label
input.post-check(type='checkbox', name='delete_file' value=1)
input.post-check(type='checkbox', name='delete_file' value='Delete files')
| Delete Files
label
input.post-check(type='checkbox', name='ban' value=1)
input.post-check(type='checkbox', name='ban' value='Ban')
| Ban Poster
label
input.post-check(type='checkbox', name='global_ban' value=1)
input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster
label
input.post-check(type='checkbox', name='preserve_post' value=1)
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban
label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
label
input(type='text', name='ban_duration', placeholder='ban duration e.g. 7d' autocomplete='off')
label
input.post-check(type='checkbox', name='sticky' value=1)
input.post-check(type='checkbox', name='sticky' value='Sticky')
| Toggle Sticky
label
input.post-check(type='checkbox', name='lock' value=1)
input.post-check(type='checkbox', name='lock' value='Lock')
| Toggle Lock
label
input.post-check(type='checkbox', name='sage' value=1)
input.post-check(type='checkbox', name='sage' value='Sage')
| Toggle Sage
label
input.post-check(type='checkbox', name='cyclic' value=1)
input.post-check(type='checkbox', name='cyclic' value='Cycle')
| Toggle Cycle
.actions
h4.no-m-p Captcha:

@ -3,25 +3,25 @@ details.toggle-label
.actions
h4.no-m-p Actions:
label
input.post-check(type='checkbox', name='delete' value=1)
input.post-check(type='checkbox', name='delete' value='Delete post')
| Delete Posts
label
input.post-check(type='checkbox', name='delete_file' value=1)
input.post-check(type='checkbox', name='delete_file' value='Delete files')
| Delete Files
label
input.post-check(type='checkbox', name='spoiler' value=1)
input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
| Spoiler Files
label
input.post-check(type='checkbox', name='delete_ip_global' value=1)
input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
| Delete from IP globally
label
input.post-check(type='checkbox', name='global_dismiss' value=1)
input.post-check(type='checkbox', name='global_dismiss' value='Dismiss global reports')
| Dismiss Global Reports
label
input.post-check(type='checkbox', name='global_ban' value=1)
input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster
label
input.post-check(type='checkbox', name='preserve_post' value=1)
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban
label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')

@ -3,36 +3,36 @@ details.toggle-label
.actions
h4.no-m-p Actions:
label
input.post-check(type='checkbox', name='delete' value=1)
input.post-check(type='checkbox', name='delete' value='Delete post')
| Delete Posts
label
input.post-check(type='checkbox', name='delete_file' value=1)
input.post-check(type='checkbox', name='delete_file' value='Delete files')
| Delete Files
label
input.post-check(type='checkbox', name='spoiler' value=1)
input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
| Spoiler Files
label
input.post-check(type='checkbox', name='global_report' value=1)
input.post-check(type='checkbox', name='global_report' value='Global report')
| Global Report
label
input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
label
input.post-check(type='checkbox', name='delete_ip_board' value=1)
input.post-check(type='checkbox', name='delete_ip_board' value='Delete posts by IP')
| Delete from IP on board
label
input.post-check(type='checkbox', name='delete_ip_global' value=1)
input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
| Delete from IP globally
label
input.post-check(type='checkbox', name='dismiss' value=1)
input.post-check(type='checkbox', name='dismiss' value='Dismiss reports')
| Dismiss Reports
label
input.post-check(type='checkbox', name='ban' value=1)
input.post-check(type='checkbox', name='ban' value='Ban')
| Ban Poster
label
input.post-check(type='checkbox', name='global_ban' value=1)
input.post-check(type='checkbox', name='global_ban' value='Ban global')
| Global Ban Poster
label
input.post-check(type='checkbox', name='preserve_post' value=1)
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
| Show Post In Ban
label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')

@ -1,4 +1,4 @@
section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100')
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br
h1.board-title Banners (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/])

@ -1,5 +1,5 @@
section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` alt='banner image')
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br
a.no-decoration(href=`/${board._id}/index.html`)
h1.board-title /#{board._id}/ - #{board.settings.name}

@ -1,4 +1,4 @@
section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100')
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br
h1.board-title Catalog (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/])

@ -0,0 +1,4 @@
section.board-header
img.board-banner(src=`/randombanner?board=${board._id}` width='300' height='100' alt='banner image')
br
h1.board-title Logs (#[a.no-decoration(href=`/${board._id}/index.html`) /#{board._id}/])

@ -7,4 +7,4 @@ if maxPage === 0
else
a(href=`/${board._id}/${i === 1 ? 'index' : i}.html`) #{i}
|
| |
| |

@ -3,7 +3,7 @@ mixin catalogtile(board, post, index)
- const postURL = `/${board._id}/thread/${post.postId}.html#${post.postId}`
header.post-info
include ../includes/postmods.pug
a.no-decoration.post-subject(href=postURL) #{post.subject || '#'+post.postId}
a.no-decoration.post-subject(href=postURL) #{post.subject || 'No subject'} #{'#'+post.postId}
| -
span(title='Replies') R: #{post.replyposts}
| /

@ -13,6 +13,8 @@ block content
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a.bold(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
section.catalog
if board.banners.length > 0
@ -28,3 +30,5 @@ block content
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a.bold(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]

@ -13,9 +13,12 @@ block content
include ../includes/stickynav.pug
nav.pages
include ../includes/pages.pug
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
form(action='/forms/board/'+board._id+'/actions' method='POST' enctype='application/x-www-form-urlencoded')
if threads.length === 0
@ -29,7 +32,10 @@ block content
hr(size=1)
nav.pages
include ../includes/pages.pug
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
include ../includes/actionfooter.pug

@ -14,6 +14,8 @@ block content
a.bold(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
if threads.length === 0
p No posts.
@ -27,3 +29,5 @@ block content
a.bold(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]

@ -0,0 +1,40 @@
extends ../layout.pug
block head
title Logs for #{startDate.toDateString()}
block content
include ../includes/logheader.pug
br
include ../includes/stickynav.pug
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
.table-container.flex-center.mv-10.text-center
table
tr
th Date
th User
th Actions
th Post IDs
for log in logs
tr
td #{log.date.toLocaleString()}
td #{log.user}
td #{log.actions}
td #{log.postIds}
hr(size=1)
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]

@ -0,0 +1,40 @@
extends ../layout.pug
block head
title Log list for /#{board._id}/
block content
include ../includes/logheader.pug
br
include ../includes/stickynav.pug
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
if dates.length === 0
p No Logs.
else
.table-container.flex-center.mv-10.text-center
table
for date in dates
tr
-
const day = ('0'+date.getDate()).slice(-2);
const month = ('0'+(date.getMonth()+1)).slice(-2);
const year = date.getFullYear();
const logName = `${month}-${day}-${year}`;
td: a(href=`logs/${logName}.html`) #{logName}
hr(size=1)
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a.bold(href=`/${board._id}/logs.html`) [Logs]

@ -28,6 +28,8 @@ block content
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
hr(size=1)
form(action=`/forms/board/${board._id}/actions` method='POST' enctype='application/x-www-form-urlencoded')
section.thread
@ -37,8 +39,10 @@ block content
hr(size=1)
nav.pages
a(href=`/${board._id}/index.html`) [Index]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/catalog.html`) [Catalog]
|
a(href=`/${board._id}/banners.html`) [Banners]
|
a(href=`/${board._id}/logs.html`) [Logs]
include ../includes/actionfooter.pug

Loading…
Cancel
Save