accounts page, list owned and mod boards in accounts, show on global manage and accounts page

merge-requests/208/head
fatchan 5 years ago
parent ff22b3650c
commit 2b4e631756
  1. 2
      controllers/forms/editaccounts.js
  2. 2
      controllers/forms/transfer.js
  3. 3
      controllers/pages.js
  4. 83
      db/accounts.js
  5. 6
      db/boards.js
  6. 4
      helpers/checks/isloggedin.js
  7. 4
      helpers/sessionrefresh.js
  8. 30
      models/forms/changeboardsettings.js
  9. 10
      models/forms/create.js
  10. 4
      models/forms/deleteboard.js
  11. 56
      models/forms/editaccounts.js
  12. 8
      models/forms/login.js
  13. 4
      models/forms/transferboard.js
  14. 9
      models/pages/account.js
  15. 1
      models/pages/index.js
  16. 4
      queue.js
  17. 8
      redis.js
  18. 2
      views/includes/navbar.pug
  19. 49
      views/pages/account.pug
  20. 10
      views/pages/globalmanageaccounts.pug

@ -9,7 +9,7 @@ module.exports = async (req, res, next) => {
if (!req.body.checkedaccounts || req.body.checkedaccounts.length === 0 || req.body.checkedaccounts.length > 10) { if (!req.body.checkedaccounts || req.body.checkedaccounts.length === 0 || req.body.checkedaccounts.length > 10) {
errors.push('Must select 1-10 accounts'); errors.push('Must select 1-10 accounts');
} }
if (!req.body.auth_level && !req.body.delete_account) { if (typeof req.body.auth_level !== 'number' && !req.body.delete_account) {
errors.push('Missing auth level or delete action'); errors.push('Missing auth level or delete action');
} }
if (typeof req.body.auth_level === 'number' && req.body.auth_level < 0 || req.body.auth_level > 4) { if (typeof req.body.auth_level === 'number' && req.body.auth_level < 0 || req.body.auth_level > 4) {

@ -14,7 +14,7 @@ module.exports = async (req, res, next) => {
errors.push('Transfer username must be at less than 50 characters'); errors.push('Transfer username must be at less than 50 characters');
} }
if (req.body.username === res.locals.board.owner) { if (req.body.username === res.locals.board.owner) {
errors.push('You are already board owner...'); errors.push('New owner username must not be same as old owner');
} }
if (alphaNumericRegex.test(req.body.username) !== true) { if (alphaNumericRegex.test(req.body.username) !== true) {
errors.push('URI must contain a-z 0-9 only'); errors.push('URI must contain a-z 0-9 only');

@ -16,7 +16,7 @@ const express = require('express')
, { globalManageReports, globalManageBans, globalManageRecent, globalManageAccounts, globalManageNews } = require(__dirname+'/../models/pages/globalmanage/') , { globalManageReports, globalManageBans, globalManageRecent, globalManageAccounts, globalManageNews } = require(__dirname+'/../models/pages/globalmanage/')
, { changePassword, home, register, login, logout, create, , { changePassword, home, register, login, logout, create,
board, catalog, banners, randombanner, news, captchaPage, board, catalog, banners, randombanner, news, captchaPage,
captcha, thread, modlog, modloglist, boardlist } = require(__dirname+'/../models/pages/'); captcha, thread, modlog, modloglist, account, boardlist } = require(__dirname+'/../models/pages/');
//homepage //homepage
router.get('/index.html', home); router.get('/index.html', home);
@ -60,6 +60,7 @@ router.get('/captcha', captcha); //get captcha image and cookie
router.get('/captcha.html', captchaPage); //iframed for noscript users router.get('/captcha.html', captchaPage); //iframed for noscript users
//accounts //accounts
router.get('/account.html', sessionRefresh, isLoggedIn, account); //page showing boards you are mod/owner of, links to password rese, logout, etc
router.get('/login.html', login); router.get('/login.html', login);
router.get('/register.html', register); router.get('/register.html', register);
router.get('/changepassword.html', changePassword); router.get('/changepassword.html', changePassword);

@ -32,18 +32,20 @@ module.exports = {
'_id': username, '_id': username,
'passwordHash': passwordHash, 'passwordHash': passwordHash,
'authLevel': authLevel, 'authLevel': authLevel,
'ownedBoards': [],
'modBoards': []
}); });
}, },
changePassword: async (username, newPassword) => { changePassword: async (username, newPassword) => {
const passwordHash = await bcrypt.hash(newPassword, 12); const passwordHash = await bcrypt.hash(newPassword, 12);
return db.updateOne({ return db.updateOne({
'_id': username '_id': username
}, { }, {
'$set': { '$set': {
'passwordHash': passwordHash 'passwordHash': passwordHash
} }
}); });
}, },
find: (skip=0, limit=0) => { find: (skip=0, limit=0) => {
@ -64,6 +66,75 @@ module.exports = {
}); });
}, },
addOwnedBoard: (username, board) => {
return db.updateOne({
'_id': username
}, {
'$addToSet': {
'ownedBoards': board
}
});
},
removeOwnedBoard: (username, board) => {
return db.updateOne({
'_id': username
}, {
'$pull': {
'ownedBoards': board
}
});
},
addModBoard: (usernames, board) => {
return db.updateMany({
'_id': {
'$in': usernames
}
}, {
'$addToSet': {
'modBoards': board
}
});
},
removeModBoard: (usernames, board) => {
return db.updateMany({
'_id': {
'$in': usernames
}
}, {
'$pull': {
'modBoards': board
}
});
},
getOwnedOrModBoards: (usernames) => {
return db.find({
'_id': {
'$in': usernames
},
'$or': [
{
'ownedBoards.0': {
'$exists': true
},
},
{
'modBoards.0': {
'$exists': true
}
}
]
}, {
'projection': {
'ownedBoards': 1,
'modBoards': 1,
}
}).toArray();
},
setLevel: (usernames, level) => { setLevel: (usernames, level) => {
//increase users auth level //increase users auth level
return db.updateMany({ return db.updateMany({

@ -131,7 +131,11 @@ module.exports = {
}, },
count: (showUnlisted=false) => { count: (showUnlisted=false) => {
return db.countDocuments(showUnlisted ? {} : { 'settings.unlisted': false }); if (showUnlisted) {
return db.countDocuments({ 'settings.unlisted': false });
} else {
return db.estimatedDocumentCount();
}
}, },
totalStats: () => { totalStats: () => {

@ -1,7 +1,7 @@
'use strict'; 'use strict';
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {
if (req.session.authenticated === true) { if (req.session && req.session.authenticated === true) {
return next(); return next();
} }
let goto; let goto;
@ -9,5 +9,5 @@ module.exports = async (req, res, next) => {
//coming from a GET page isLoggedIn middleware check //coming from a GET page isLoggedIn middleware check
goto = req.path; goto = req.path;
} }
res.redirect(`/login.html${goto ? '?goto='+goto : ''}`); return res.redirect(`/login.html${goto ? '?goto='+goto : ''}`);
} }

@ -11,7 +11,9 @@ module.exports = async (req, res, next) => {
} else { } else {
req.session.user = { req.session.user = {
'username': account._id, 'username': account._id,
'authLevel': account.authLevel 'authLevel': account.authLevel,
'modBoards': account.modBoards,
'ownedBoards': account.ownedBoards,
}; };
} }
} }

@ -13,8 +13,12 @@ const { Boards, Posts, Accounts } = require(__dirname+'/../../db/')
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {
//oldsettings before changes
const oldSettings = res.locals.board.settings; const oldSettings = res.locals.board.settings;
//array of promises we might need
const promises = [];
let markdownAnnouncement; let markdownAnnouncement;
if (req.body.announcement !== oldSettings.announcement.raw) { if (req.body.announcement !== oldSettings.announcement.raw) {
//remarkup the announcement if it changes //remarkup the announcement if it changes
@ -24,17 +28,34 @@ module.exports = async (req, res, next) => {
markdownAnnouncement = sanitized; markdownAnnouncement = sanitized;
} }
let moderators = req.body.moderators != null ? req.body.moderators.split('\r\n').filter(n => n).slice(0,10) : oldSettings.moderators let moderators = req.body.moderators != null ? req.body.moderators.split('\r\n').filter(n => n).slice(0,10) : [];
if (moderators !== oldSettings.moderators) { if (moderators.length === 0 && oldSettings.moderators.length > 0) {
//make sure moderators actually have existing accounts //remove all mods if mod list being emptied
promises.push(Accounts.removeModBoard(oldSettings.moderators, req.params.board));
} else if (moderators !== oldSettings.moderators) {
if (moderators.length > 0) { if (moderators.length > 0) {
//make sure moderators actually have existing accounts
const validCount = await Accounts.countUsers(moderators); const validCount = await Accounts.countUsers(moderators);
if (validCount !== moderators.length) { if (validCount !== moderators.length) {
//some usernames were not valid, reset to old setting
moderators = oldSettings.moderators; moderators = oldSettings.moderators;
} else {
//all accounts exist, check added/removed
const modsRemoved = oldSettings.moderators.filter(m => !moderators.includes(m));
const modsAdded = moderators.filter(m => !oldSettings.moderators.includes(m));
if (modsRemoved.length > 0) {
//remove mod from accounts
promises.push(Accounts.removeModBoard(modsRemoved, req.params.board));
}
if (modsAdded.length > 0) {
//add mod to accounts
promises.push(Accounts.addModBoard(modsAdded, req.params.board));
}
} }
} }
} }
//todo: make separate functions for handling array, boolean, number, text settings.
const newSettings = { const newSettings = {
moderators, moderators,
'name': req.body.name && req.body.name.trim().length > 0 ? req.body.name : oldSettings.name, 'name': req.body.name && req.body.name.trim().length > 0 ? req.body.name : oldSettings.name,
@ -92,9 +113,6 @@ module.exports = async (req, res, next) => {
//update this in locals incase is used in later parts //update this in locals incase is used in later parts
res.locals.board.settings = newSettings; res.locals.board.settings = newSettings;
//array of promises we might need
const promises = [];
//pages in new vs old settings //pages in new vs old settings
const oldMaxPage = Math.ceil(oldSettings.threadLimit/10); const oldMaxPage = Math.ceil(oldSettings.threadLimit/10);
const newMaxPage = Math.ceil(newSettings.threadLimit/10); const newMaxPage = Math.ceil(newSettings.threadLimit/10);

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const { Boards } = require(__dirname+'/../../db/') const { Boards, Accounts } = require(__dirname+'/../../db/')
, { boardDefaults } = require(__dirname+'/../../configs/main.js'); , { boardDefaults } = require(__dirname+'/../../configs/main.js');
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {
@ -8,6 +8,7 @@ module.exports = async (req, res, next) => {
const { name, description } = req.body const { name, description } = req.body
, uri = req.body.uri.toLowerCase() , uri = req.body.uri.toLowerCase()
, tags = req.body.tags.split('\n').filter(n => n) , tags = req.body.tags.split('\n').filter(n => n)
, owner = req.session.user.username
, board = await Boards.findOne(uri); , board = await Boards.findOne(uri);
// if board exists reject // if board exists reject
@ -22,7 +23,7 @@ module.exports = async (req, res, next) => {
//todo: add a settings for defaults //todo: add a settings for defaults
const newBoard = { const newBoard = {
'_id': uri, '_id': uri,
'owner': req.session.user.username, owner,
'banners': [], 'banners': [],
'sequence_value': 1, 'sequence_value': 1,
'pph': 0, 'pph': 0,
@ -37,7 +38,10 @@ module.exports = async (req, res, next) => {
} }
} }
await Boards.insertOne(newBoard); await Promise.all([
Boards.insertOne(newBoard),
Accounts.addOwnedBoard(owner, uri)
]);
return res.redirect(`/${uri}/index.html`); return res.redirect(`/${uri}/index.html`);

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const { Boards, Stats, Posts, Bans, Modlogs } = require(__dirname+'/../../db/') const { Accounts, Boards, Stats, Posts, Bans, Modlogs } = require(__dirname+'/../../db/')
, cache = require(__dirname+'/../../redis.js') , cache = require(__dirname+'/../../redis.js')
, deletePosts = require(__dirname+'/deletepost.js') , deletePosts = require(__dirname+'/deletepost.js')
, uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js') , uploadDirectory = require(__dirname+'/../../helpers/files/uploadDirectory.js')
@ -17,6 +17,8 @@ module.exports = async (uri, board) => {
await deletePosts(allPosts, uri, true); await deletePosts(allPosts, uri, true);
} }
await Promise.all([ await Promise.all([
Accounts.removeOwnedBoard(board.owner, uri), //remove board from owner account
board.settings.moderators.length > 0 ? Accounts.removeModBoard(board.settings.moderators) : void 0, //remove board from mods accounts
Modlogs.deleteBoard(uri), //modlogs for the board Modlogs.deleteBoard(uri), //modlogs for the board
Bans.deleteBoard(uri), //bans for the board Bans.deleteBoard(uri), //bans for the board
Stats.deleteBoard(uri), //stats for the board Stats.deleteBoard(uri), //stats for the board

@ -1,21 +1,67 @@
'use strict'; 'use strict';
const { Accounts } = require(__dirname+'/../../db/'); const { Accounts, Boards } = require(__dirname+'/../../db/')
, cache = require(__dirname+'/../../redis.js')
module.exports = async (req, res, next) => { module.exports = async (req, res, next) => {
//edit the accounts //edit the accounts
let amount = 0; let amount = 0;
if (req.body.delete_account) { if (req.body.delete_account) {
const accountsWithBoards = await Accounts.getOwnedOrModBoards(req.body.checkedaccounts);
if (accountsWithBoards.length > 0) {
const bulkWrites = [];
for (let i = 0; i < accountsWithBoards.length; i++) {
const acc = accountsWithBoards[i];
if (acc.modBoards.length > 0) {
//remove from moderators of any boards they are mod on
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': acc.modBoards
}
},
'update': {
'$pull': {
'settings.moderators': acc._id
}
}
}
});
cache.del(acc.modBoards.map(b => `board_${b}`));
}
if (acc.ownedBoards.length > 0) {
//remove from moderators of any boards they are mod on
bulkWrites.push({
'updateMany': {
'filter': {
'_id': {
'$in': acc.ownedBoards
}
},
'update': {
'$set': {
'owner': null //board has no owner
}
}
}
});
cache.del(acc.ownedBoards.map(b => `board_${b}`));
//todo: use list of board with no owners for claims
}
}
await Boards.db.bulkWrite(bulkWrites);
}
amount = await Accounts.deleteMany(req.body.checkedaccounts).then(res => res.deletedCount); amount = await Accounts.deleteMany(req.body.checkedaccounts).then(res => res.deletedCount);
} else { } else {
amount = await Accounts.setLevel(req.body.checkedaccounts, req.body.auth_level).then(res => res.modifiedCount); amount = await Accounts.setLevel(req.body.checkedaccounts, req.body.auth_level).then(res => res.modifiedCount);
} }
return res.render('message', { return res.render('message', {
'title': 'Success', 'title': 'Success',
'message': `${req.body.delete_account ? 'Deleted' : 'Edited'} ${amount} accounts`, 'message': `${req.body.delete_account ? 'Deleted' : 'Edited'} ${amount} accounts`,
'redirect': '/globalmanage/accounts.html' 'redirect': '/globalmanage/accounts.html'
}); });
} }

@ -7,7 +7,7 @@ module.exports = async (req, res, next) => {
const username = req.body.username.toLowerCase(); const username = req.body.username.toLowerCase();
const password = req.body.password; const password = req.body.password;
const goto = req.body.goto; const goto = req.body.goto || '/account.html';
const failRedirect = `/login.html${goto ? '?goto='+goto : ''}` const failRedirect = `/login.html${goto ? '?goto='+goto : ''}`
//fetch an account //fetch an account
@ -31,12 +31,14 @@ module.exports = async (req, res, next) => {
// add the account to the session and authenticate if password was correct // add the account to the session and authenticate if password was correct
req.session.user = { req.session.user = {
'username': account._id, 'username': account._id,
'authLevel': account.authLevel 'authLevel': account.authLevel,
'ownedBoards': account.ownedBoards,
'modBoards': account.modBoards,
}; };
req.session.authenticated = true; req.session.authenticated = true;
//successful login //successful login
return res.redirect(goto || '/'); return res.redirect(goto);
} }

@ -14,6 +14,10 @@ module.exports = async (req, res, next) => {
}); });
} }
//modify accounts with new board ownership
await Accounts.removeOwnedBoard(res.locals.board.owner, req.params.board)
await Accounts.addOwnedBoard(newOwner._id, req.params.board);
//set owner in memory and in db //set owner in memory and in db
res.locals.board.owner = newOwner._id; res.locals.board.owner = newOwner._id;
await Boards.setOwner(req.params.board, res.locals.board.owner); await Boards.setOwner(req.params.board, res.locals.board.owner);

@ -0,0 +1,9 @@
'use strict';
module.exports = async (req, res, next) => {
return res.render('account', {
user: req.session.user
});
}

@ -3,6 +3,7 @@
module.exports = { module.exports = {
changePassword: require(__dirname+'/changepassword.js'), changePassword: require(__dirname+'/changepassword.js'),
register: require(__dirname+'/register.js'), register: require(__dirname+'/register.js'),
account: require(__dirname+'/account.js'),
home: require(__dirname+'/home.js'), home: require(__dirname+'/home.js'),
login: require(__dirname+'/login.js'), login: require(__dirname+'/login.js'),
logout: require(__dirname+'/logout.js'), logout: require(__dirname+'/logout.js'),

@ -8,8 +8,8 @@ module.exports = {
queue: taskQueue, queue: taskQueue,
push: (data, options) => { push: (data) => {
taskQueue.add(data, options); taskQueue.add(data, { removeOnComplete: true });
} }
} }

@ -33,8 +33,12 @@ module.exports = {
}, },
//delete value with key //delete value with key
del: (key) => { del: (keyOrKeys) => {
return client.del(key); if (Array.isArray(keyOrKeys)) {
return client.del(...keyOrKeys);
} else {
return client.del(keyOrKeys);
}
}, },
deletePattern: (pattern) => { deletePattern: (pattern) => {

@ -2,6 +2,6 @@ nav.navbar#top
a.nav-item(href='/index.html') Home a.nav-item(href='/index.html') Home
a.nav-item(href='/news.html') News a.nav-item(href='/news.html') News
a.nav-item(href='/boards.html') Boards a.nav-item(href='/boards.html') Boards
a.nav-item(href='/account.html') Account
a.nav-item(href=`/${board ? board._id+'/manage/reports' : 'globalmanage/recent'}.html`) Manage a.nav-item(href=`/${board ? board._id+'/manage/reports' : 'globalmanage/recent'}.html`) Manage
a.nav-item(href='/create.html') Create
a.jsonly.nav-item.right#settings &#9881; a.jsonly.nav-item.right#settings &#9881;

@ -0,0 +1,49 @@
extends ../layout.pug
block head
script(src='/js/all.js')
title Manage
block content
.board-header
h1.board-title Welcome, #{user.username}
h4.board-description Auth level: #{user.authLevel}
br
hr(size=1)
h4.no-m-p General:
ul
if user.authLevel <= 1
li: a(href='/globalmanage/recent.html') Global management
li: a(href='/create.html') Create a board
li: a(href='/changepassword.html') Change password
li: a(href='/logout') Log out
hr(size=1)
h4.no-m-p Boards you own:
if user.ownedBoards && user.ownedBoards.length > 0
ul
for b in user.ownedBoards
li
a(href=`/${b}/index.html`) /#{b}/
| -
a(href=`/${b}/manage/reports.html`) Reports
| ,
a(href=`/${b}/manage/bans.html`) Bans
| ,
a(href=`/${b}/manage/settings.html`) Settings
| ,
a(href=`/${b}/manage/banners.html`) Banners
else
p None
hr(size=1)
h4.no-m-p Boards you moderate:
if user.modBoards && user.modBoards.length > 0
ul
for b in user.modBoards
li
a(href=`/${b}/index.html`) /#{b}/
| -
a(href=`/${b}/manage/reports.html`) Reports
| ,
a(href=`/${b}/manage/bans.html`) Bans
else
p None

@ -20,11 +20,21 @@ block content
th th
th Username th Username
th Auth Level th Auth Level
th Own Boards
th Mod Boards
for account in accounts for account in accounts
tr tr
td: input(type='checkbox', name='checkedaccounts' value=account._id) td: input(type='checkbox', name='checkedaccounts' value=account._id)
td #{account._id} td #{account._id}
td #{account.authLevel} td #{account.authLevel}
td
for b in account.ownedBoards
a(href=`/${b}/index.html`) /#{b}/
|
td
for b in account.modBoards
a(href=`/${b}/index.html`) /#{b}/
|
.pages.mt-5 .pages.mt-5
include ../includes/pages.pug include ../includes/pages.pug
.row .row

Loading…
Cancel
Save