user management enhancements

master
rob 2 years ago
parent 95792f274c
commit 7c92310483

@ -44,6 +44,7 @@ class AdminController extends SiteController {
router.use('/content-report',await this.loadChild(path.join(__dirname, 'admin', 'content-report')));
router.use('/core-node',await this.loadChild(path.join(__dirname, 'admin', 'core-node')));
router.use('/core-user',await this.loadChild(path.join(__dirname, 'admin', 'core-user')));
router.use('/host',await this.loadChild(path.join(__dirname, 'admin', 'host')));
router.use('/job-queue', await this.loadChild(path.join(__dirname, 'admin', 'job-queue')));
router.use('/log', await this.loadChild(path.join(__dirname, 'admin', 'log')));

@ -0,0 +1,96 @@
// admin/core-user.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const express = require('express');
// const multer = require('multer');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class CoreUserController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
}
async start ( ) {
// const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/upload` });
const router = express.Router();
router.use(async (req, res, next) => {
res.locals.currentView = 'admin';
res.locals.adminView = 'core-user';
return next();
});
router.param('coreUserId', this.populateCoreUserId.bind(this));
router.post('/:coreUserId', this.postUpdateCoreUser.bind(this));
router.get('/:coreUserId', this.getCoreUserView.bind(this));
router.get('/', this.getIndex.bind(this));
return router;
}
async populateCoreUserId (req, res, next, coreUserId) {
const { coreNode: coreNodeService } = this.dtp.services;
try {
res.locals.userAccount = await coreNodeService.getUserByLocalId(coreUserId);
if (!res.locals.userAccount) {
throw new SiteError(404, 'Core Member not found');
}
return next();
} catch (error) {
this.log.error('failed to populate coreUserId', { coreUserId, error });
return next(error);
}
}
async postUpdateCoreUser (req, res, next) {
const { coreNode: coreNodeService } = this.dtp.services;
try {
await coreNodeService.updateUserForAdmin(res.locals.userAccount, req.body);
res.redirect('/admin/core-user');
} catch (error) {
return next(error);
}
}
async getCoreUserView (req, res, next) {
const { comment: commentService } = this.dtp.services;
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.recentComments = await commentService.getForAuthor(res.locals.userAccount, res.locals.pagination);
res.render('admin/core-user/form');
} catch (error) {
this.log.error('failed to produce user view', { error });
return next(error);
}
}
async getIndex (req, res, next) {
const { coreNode: coreNodeService } = this.dtp.services;
const search = { };
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.users = await coreNodeService.searchUsers(search, res.locals.pagination);
res.render('admin/core-user/index');
} catch (error) {
this.log.error('failed to render Core User home', {
search,
pagination: res.locals.pagination,
error,
});
return next(error);
}
}
}
module.exports = {
name: 'adminCoreUser',
slug: 'admin-core-user',
create: async (dtp) => { return new CoreUserController(dtp); },
};

@ -16,7 +16,7 @@ const CoreUserSchema = new Schema({
core: { type: Schema.ObjectId, required: true, ref: 'CoreNode' },
coreUserId: { type: Schema.ObjectId, required: true },
username: { type: String, required: true },
username_lc: { type: String, required: true, lowercase: true },
username_lc: { type: String, required: true, lowercase: true, index: 1 },
displayName: { type: String },
bio: { type: String, maxlength: 300 },
flags: { type: UserFlagsSchema, select: false },

@ -12,6 +12,7 @@ module.exports.DTP_THEME_LIST = ['dtp-light', 'dtp-dark'];
module.exports.UserFlagsSchema = new Schema({
isAdmin: { type: Boolean, default: false, required: true },
isModerator: { type: Boolean, default: false, required: true },
isEmailVerified: { type: Boolean, default: false, required: true },
});
module.exports.UserPermissionsSchema = new Schema({

@ -16,6 +16,8 @@ const CoreNodeRequest = mongoose.model('CoreNodeRequest');
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2');
const striptags = require('striptags');
const { SiteService, SiteError } = require('../../lib/site-lib');
class CoreAddress {
@ -382,6 +384,22 @@ class CoreNodeService extends SiteService {
return cores;
}
async searchUsers (search, pagination) {
const users = await CoreUser
.find(search)
.select('+flags +permissions +optIn')
.sort({ username_lc: 1 })
.skip(pagination.skip)
.limit(pagination.cpp)
.populate(this.populateCoreUser)
.lean();
return users.map((user) => {
user.type = 'CoreUser';
return user;
});
}
async getUserByLocalId (userId) {
const user = await CoreUser
.findOne({ _id: userId })
@ -392,6 +410,42 @@ class CoreNodeService extends SiteService {
return user;
}
async updateUserForAdmin (user, settings) {
const NOW = new Date();
if (!settings.username || !settings.username.length) {
throw new SiteError(406, 'Must include username');
}
settings.username = striptags(settings.username.trim());
settings.username_lc = settings.username.toLowerCase();
await CoreUser.updateOne(
{ _id: user._id },
{
$set: {
updated: NOW,
username: settings.username,
username_lc: settings.username_lc,
displayName: striptags(settings.displayName.trim()),
bio: striptags(settings.bio.trim()),
'flags.isAdmin': settings.isAdmin === 'on',
'flags.isModerator': settings.isModerator === 'on',
'flags.isEmailVerified': settings.isEmailVerified === 'on',
'permissions.canLogin': settings.canLogin === 'on',
'permissions.canChat': settings.canChat === 'on',
'permissions.canComment': settings.canComment === 'on',
'permissions.canReport': settings.canReport === 'on',
'optIn.system': settings.optInSystem === 'on',
'optIn.marketing': settings.optInMarketing === 'on',
},
},
);
}
async updateUserSettings (user, settings) {
await CoreUser.updateOne(
{ _id: user._id },

@ -393,6 +393,21 @@ class OAuth2Service extends SiteService {
}
return done(null, token.user, { scope: token.scope });
}
/**
* Retrieves OAuth2 access/refresh tokens for a specific CoreUser.
* @param {CoreUser} user The user for which tokens are wanted.
* @param {*} type The type of token wanted (access or refresh), or don't
* specify to receive all tokens (unfiltered).
* @returns Array of tokens for the specified user, if any.
*/
async getUserTokens (user, type) {
const tokens = await OAuth2Token
.find({ user: user._id, type })
.populate(this.populateOAuth2Token)
.lean();
return tokens;
}
}
module.exports = {

@ -97,6 +97,7 @@ class UserService extends SiteService {
user.flags = {
isAdmin: false,
isModerator: false,
isEmailVerified: false,
};
user.permissions = {
@ -229,12 +230,19 @@ class UserService extends SiteService {
username: userDefinition.username,
username_lc,
displayName: userDefinition.displayName,
bio: striptags(userDefinition.bio.trim()),
'flags.isAdmin': userDefinition.isAdmin === 'on',
'flags.isModerator': userDefinition.isModerator === 'on',
'flags.isEmailVerified': userDefinition.isEmailVerified === 'on',
'permissions.canLogin': userDefinition.canLogin === 'on',
'permissions.canChat': userDefinition.canChat === 'on',
'permissions.canComment': userDefinition.canComment === 'on',
'permissions.canReport': userDefinition.canReport === 'on',
'optIn.system': userDefinition.optInSystem === 'on',
'optIn.marketing': userDefinition.optInMarketing === 'on',
},
},
);

@ -32,6 +32,11 @@ ul.uk-nav.uk-nav-default
span.nav-item-icon
i.fas.fa-project-diagram
span.uk-margin-small-left Core Nodes
li(class={ 'uk-active': (adminView === 'core-user') })
a(href="/admin/core-user")
span.nav-item-icon
i.fas.fa-user-astronaut
span.uk-margin-small-left Core Users
li(class={ 'uk-active': (adminView === 'core-node') })
a(href="/admin/service-node")

@ -0,0 +1,120 @@
extends ../layouts/main
block content
include ../../comment/components/comment-review
div(uk-grid).uk-grid-small
div(class="uk-width-1-1 uk-width-2-3@l")
form(method="POST", action=`/admin/core-user/${userAccount._id}`).uk-form
input(type="hidden", name="username", value= userAccount.username)
input(type="hidden", name="displayName", value= userAccount.displayName)
.uk-card.uk-card-default.uk-card-small
.uk-card-header
if userAccount.displayName
.uk-text-large= userAccount.displayName
div
a(href=`/user/${userAccount._id}`) @#{userAccount.username}
.uk-card-body
.uk-margin
label(for="bio").uk-form-label Bio
textarea(id="bio", name="bio", rows="3", placeholder="Enter profile bio").uk-textarea= userAccount.bio
.uk-margin
label.uk-form-label Flags
div(uk-grid).uk-grid-small
label
input(id="is-admin", name="isAdmin", type="checkbox", checked= userAccount.flags.isAdmin)
| Admin
label
input(id="is-moderator", name="isModerator", type="checkbox", checked= userAccount.flags.isModerator)
| Moderator
label
input(id="is-email-verified", name="isEmailVerified", type="checkbox", checked= userAccount.flags.isEmailVerified)
| Email Verified
.uk-margin
label.uk-form-label Permissions
.uk-margin
div(uk-grid).uk-grid-small
label
input(id="can-login", name="canLogin", type="checkbox", checked= userAccount.permissions.canLogin)
| Can Login
label
input(id="can-chat", name="canChat", type="checkbox", checked= userAccount.permissions.canChat)
| Can Chat
label
input(id="can-comment", name="canComment", type="checkbox", checked= userAccount.permissions.canComment)
| Can Comment
label
input(id="can-report", name="canReport", type="checkbox", checked= userAccount.permissions.canReport)
| Can Report
.uk-margin
label.uk-form-label Opt-Ins
div(uk-grid).uk-grid-small
label
input(id="optin-system", name="optInSystem", type="checkbox", checked= userAccount.optIn.system)
| System
label
input(id="optin-marketing", name="optInMarketing", type="checkbox", checked= userAccount.optIn.marketing)
| Marketing
.uk-card-footer
div(uk-grid).uk-grid-small
.uk-width-expand
+renderBackButton()
.uk-width-auto
button(type="submit").uk-button.uk-button-primary Update User
div(class="uk-width-1-1 uk-width-1-3@l")
.uk-margin
.uk-card.uk-card-default.uk-card-small
.uk-card-header
h4.uk-card-title
div(uk-grid).uk-grid-small
.uk-width-expand
div= userAccount.core.meta.name
.uk-width-auto
img(src="/img/icon/dtp-core.svg", alt="DTP Core Icon", style="height: 1em; width: auto;")
.uk-card-body
.uk-margin
label.uk-form-label Description
div= userAccount.core.meta.description
.uk-margin
div(uk-grid)
.uk-width-auto
label.uk-form-label Name
div= userAccount.core.meta.name
.uk-width-auto
label.uk-form-label Created
div= moment(userAccount.core.created).fromNow()
.uk-width-auto
label.uk-form-label Updated
div= moment(userAccount.core.updated).fromNow()
.uk-margin
div(uk-grid)
.uk-width-auto
label.uk-form-label Domain
div= userAccount.core.meta.domain
.uk-width-auto
label.uk-form-label Domain Key
div= userAccount.core.meta.domainKey
.uk-margin
.uk-card.uk-card-default.uk-card-small
.uk-card-header
h4.uk-card-title Recent Comments
.uk-card-body
if Array.isArray(recentComments) && (recentComments.length > 0)
ul.uk-list.uk-list-divider
each comment in recentComments
li
+renderCommentReview(comment)
else
div #{userAccount.displayName || userAccount.uslername} has no recent comments.

@ -0,0 +1,33 @@
extends ../layouts/main
block content
h1 Core Users
if Array.isArray(users) && (users.length > 0)
.uk-overflow-auto
table.uk-table.uk-table-divider.uk-table-small.uk-table-justify
thead
th Username
th Display Name
th Created
th Core
th Core Domain
th Core User ID
th User ID
tbody
each userAccount in users
tr
td
a(href=`/admin/core-user/${userAccount._id}`)= userAccount.username
td
if userAccount.displayName
a(href=`/admin/core-user/${userAccount._id}`)= userAccount.displayName
else
.uk-text-muted N/A
td= moment(userAccount.created).format('YYYY-MM-DD hh:mm a')
td= userAccount.core.meta.name
td= userAccount.core.meta.domainKey
td= userAccount.coreUserId
td= userAccount._id
else
div There are no Core users.

@ -8,63 +8,81 @@ block content
form(method="POST", action=`/admin/user/${userAccount._id}`).uk-form
input(type="hidden", name="username", value= userAccount.username)
input(type="hidden", name="displayName", value= userAccount.displayName)
.uk-card.uk-card-secondary.uk-card-small
.uk-card.uk-card-default.uk-card-small
.uk-card-header
if userAccount.displayName
.uk-text-large= userAccount.displayName
div
a(href=`mailto:${userAccount.email}`)= userAccount.email
div
a(href=`/user/${userAccount._id}`) @#{userAccount.username}
div(uk-grid).uk-grid-small.uk-flex-middle
if userAccount.picture
.uk-width-auto
+renderProfileIcon(userAccount)
.uk-width-expand
.uk-text-large= userAccount.displayName || userAccount.username
div(uk-grid).uk-grid-small.uk-flex-between
.uk-width-auto
a(href=`mailto:${userAccount.email}`)= userAccount.email
.uk-width-auto
a(href=`/user/${userAccount._id}`) @#{userAccount.username}
.uk-card-body
.uk-margin
div(uk-grid)
div(class="uk-width-1-1 uk-width-1-2@m")
fieldset
legend Flags
.uk-margin
div(uk-grid).uk-grid-small
label
input(id="is-admin", name="isAdmin", type="checkbox", checked= userAccount.flags.isAdmin)
| Admin
label
input(id="is-moderator", name="isModerator", type="checkbox", checked= userAccount.flags.isModerator)
| Moderator
label(for="bio").uk-form-label Bio
textarea(id="bio", name="bio", rows="3", placeholder="Enter profile bio").uk-textarea= userAccount.bio
div(class="uk-width-1-1 uk-width-1-2@m")
fieldset
legend Permissions
.uk-margin
div(uk-grid).uk-grid-small
label
input(id="can-login", name="canLogin", type="checkbox", checked= userAccount.permissions.canLogin)
| Can Login
label
input(id="can-chat", name="canChat", type="checkbox", checked= userAccount.permissions.canChat)
| Can Chat
label
input(id="can-comment", name="canComment", type="checkbox", checked= userAccount.permissions.canComment)
| Can Comment
label
input(id="can-report", name="canReport", type="checkbox", checked= userAccount.permissions.canReport)
| Can Report
label
input(id="can-author-pages", name="canAuthorPages", type="checkbox", checked= userAccount.permissions.canAuthorPages)
| Can Author Pages
label
input(id="can-author-posts", name="canAuthorPosts", type="checkbox", checked= userAccount.permissions.canAuthorPosts)
| Can Author Posts
.uk-margin
label.uk-form-label Flags
div(uk-grid).uk-grid-small
label
input(id="is-admin", name="isAdmin", type="checkbox", checked= userAccount.flags.isAdmin)
| Admin
label
input(id="is-moderator", name="isModerator", type="checkbox", checked= userAccount.flags.isModerator)
| Moderator
label
input(id="is-email-verified", name="isEmailVerified", type="checkbox", checked= userAccount.flags.isEmailVerified)
| Email Verified
.uk-margin
label.uk-form-label Permissions
div(uk-grid).uk-grid-small
label
input(id="can-login", name="canLogin", type="checkbox", checked= userAccount.permissions.canLogin)
| Can Login
label
input(id="can-chat", name="canChat", type="checkbox", checked= userAccount.permissions.canChat)
| Can Chat
label
input(id="can-comment", name="canComment", type="checkbox", checked= userAccount.permissions.canComment)
| Can Comment
label
input(id="can-report", name="canReport", type="checkbox", checked= userAccount.permissions.canReport)
| Can Report
.uk-margin
label.uk-form-label Opt-Ins
div(uk-grid).uk-grid-small
label
input(id="optin-system", name="optInSystem", type="checkbox", checked= userAccount.optIn.system)
| System
label
input(id="optin-marketing", name="optInMarketing", type="checkbox", checked= userAccount.optIn.marketing)
| Marketing
button(type="submit").uk-button.dtp-button-primary.uk-display-block.uk-width-1-1 Update User
.uk-card-footer
div(uk-grid).uk-grid-small
.uk-width-expand
+renderBackButton()
.uk-width-auto
button(type="submit").uk-button.uk-button-primary Update User
div(class="uk-width-1-1 uk-width-1-3@l")
.uk-card.uk-card-secondary.uk-card-small
.uk-card.uk-card-default.uk-card-small
.uk-card-header
h4.uk-card-title #{userAccount.displayName || userAccount.username}'s Comments
h4.uk-card-title Recent Comments
.uk-card-body
ul.uk-list.uk-list-divider
each comment in recentComments
li
+renderCommentReview(comment)
if Array.isArray(recentComments) && (recentComments.length > 0)
ul.uk-list.uk-list-divider
each comment in recentComments
li
+renderCommentReview(comment)
else
div #{userAccount.displayName || userAccount.username} has no recent comments.
Loading…
Cancel
Save