Browse Source

Core vs. Local user updates (there will be more)

develop
Rob Colbert 2 years ago
parent
commit
98853ced33
  1. 9
      README.md
  2. 2
      app/controllers/admin/user.js
  3. 2
      app/controllers/hive/user.js
  4. 97
      app/controllers/user.js
  5. 7
      app/models/user.js
  6. 15
      app/services/chat.js
  7. 3
      app/services/session.js
  8. 213
      app/services/user.js
  9. 2
      app/views/admin/core-user/form.pug
  10. 2
      app/views/admin/user/form.pug
  11. 2
      app/views/components/navbar.pug
  12. 2
      app/views/components/off-canvas.pug
  13. 10
      lib/site-ioserver.js

9
README.md

@ -2,6 +2,15 @@
The base project from which all others are forked when working with my framework and system. You don't have to start from this project at all. BUT, it can save you a lot of time by simply being a 100% compatible base on which you can build your apps and sites. The base project from which all others are forked when working with my framework and system. You don't have to start from this project at all. BUT, it can save you a lot of time by simply being a 100% compatible base on which you can build your apps and sites.
## Host Preparation
The following commands must be exeucted on any host expected to run DTP Framework applications.
```sh
apt -y update && apt -y upgrade
apt -y install linux-headers-generic linux-headers-virtual linux-image-virtual linux-virtual
apt -y install build-essential ffmpeg supervisor
```
## Install Data Tier Components ## Install Data Tier Components
You will need MongoDB and MinIO installed and running before you can start DTP Base web services. You will need MongoDB and MinIO installed and running before you can start DTP Base web services.

2
app/controllers/admin/user.js

@ -68,7 +68,7 @@ class UserController extends SiteController {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
try { try {
res.locals.pagination = this.getPaginationParameters(req, 10); res.locals.pagination = this.getPaginationParameters(req, 10);
res.locals.userAccounts = await userService.getUserAccounts(res.locals.pagination, req.query.u); res.locals.userAccounts = await userService.searchLocalUserAccounts(res.locals.pagination, req.query.u);
res.locals.totalUserCount = await userService.getTotalCount(); res.locals.totalUserCount = await userService.getTotalCount();
res.render('admin/user/index'); res.render('admin/user/index');
} catch (error) { } catch (error) {

2
app/controllers/hive/user.js

@ -78,7 +78,7 @@ class HiveUserController extends SiteController {
throw new SiteError(406, 'Must include search term'); throw new SiteError(406, 'Must include search term');
} }
res.locals.q = await userService.filterUsername(req.query.q); res.locals.q = userService.filterUsername(req.query.q);
res.locals.pagination = this.getPaginationParameters(req, 20); res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.userProfiles = await userService.getUserAccounts(res.locals.pagination, res.locals.q); res.locals.userProfiles = await userService.getUserAccounts(res.locals.pagination, res.locals.q);
res.locals.userProfiles = res.locals.userProfiles.map((user) => { res.locals.userProfiles = res.locals.userProfiles.map((user) => {

97
app/controllers/user.js

@ -60,8 +60,10 @@ class UserController extends SiteController {
return next(); return next();
} }
router.param('username', this.populateUsername.bind(this)); router.param('localUsername', this.populateLocalUsername.bind(this));
router.param('userId', this.populateUserId.bind(this)); router.param('coreUsername', this.populateCoreUsername.bind(this));
router.param('localUserId', this.populateLocalUserId.bind(this));
router.param('coreUserId', this.populateCoreUserId.bind(this)); router.param('coreUserId', this.populateCoreUserId.bind(this));
router.post( router.post(
@ -73,7 +75,7 @@ class UserController extends SiteController {
); );
router.post( router.post(
'/:userId/profile-photo', '/:localUserId/profile-photo',
limiterService.createMiddleware(limiterService.config.user.postProfilePhoto), limiterService.createMiddleware(limiterService.config.user.postProfilePhoto),
checkProfileOwner, checkProfileOwner,
upload.single('imageFile'), upload.single('imageFile'),
@ -81,7 +83,7 @@ class UserController extends SiteController {
); );
router.post( router.post(
'/:userId/settings', '/:localUserId/settings',
limiterService.createMiddleware(limiterService.config.user.postUpdateSettings), limiterService.createMiddleware(limiterService.config.user.postUpdateSettings),
checkProfileOwner, checkProfileOwner,
upload.none(), upload.none(),
@ -124,7 +126,7 @@ class UserController extends SiteController {
); );
router.get( router.get(
'/:userId/settings', '/:localUsername/settings',
limiterService.createMiddleware(limiterService.config.user.getSettings), limiterService.createMiddleware(limiterService.config.user.getSettings),
authRequired, authRequired,
otpMiddleware, otpMiddleware,
@ -132,7 +134,7 @@ class UserController extends SiteController {
this.getUserSettingsView.bind(this), this.getUserSettingsView.bind(this),
); );
router.get( router.get(
'/:username', '/:localUsername',
limiterService.createMiddleware(limiterService.config.user.getUserProfile), limiterService.createMiddleware(limiterService.config.user.getUserProfile),
authRequired, authRequired,
otpMiddleware, otpMiddleware,
@ -148,48 +150,84 @@ class UserController extends SiteController {
); );
} }
async populateUsername (req, res, next, username) { async populateCoreUsername (req, res, next, coreUsername) {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
try { try {
res.locals.userProfile = await userService.getPublicProfile('User', username); res.locals.username = userService.filterUsername(coreUsername);
if (!res.locals.userProfile) { res.locals.userProfileId = await userService.getCoreUserId(res.locals.username);
throw new SiteError(404, 'Member not found'); if (!res.locals.userProfileId) {
throw new SiteError(404, 'Core member not found');
} }
return next(); // manually chain over to the ID parameter resolver
return this.populateCoreUserId(req, res, next, res.locals.userProfileId);
} catch (error) { } catch (error) {
this.log.error('failed to populate username with public profile', { username, error }); this.log.error('failed to populate core username', { coreUsername, error });
return next(error); return next(error);
} }
} }
async populateUserId (req, res, next, userId) { async populateCoreUserId (req, res, next, coreUserId) {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
try { try {
userId = mongoose.Types.ObjectId(userId); res.locals.userProfileId = mongoose.Types.ObjectId(coreUserId);
} catch (error) {
return next(new SiteError(406, 'Invalid User')); if (req.user && (req.user.type === 'CoreUser') && req.user._id.equals(res.locals.userProfileId)) {
res.locals.userProfile = await userService.getCoreUserAccount(res.locals.userProfileId);
} else {
res.locals.userProfile = await userService.getCoreUserProfile(res.locals.userProfileId);
} }
try {
res.locals.userProfile = await userService.getUserAccount(userId); if (!res.locals.userProfile) {
throw new SiteError(404, 'Core member not found');
}
return next(); return next();
} catch (error) { } catch (error) {
this.log.error('failed to populate userId', { userId, error }); this.log.error('failed to populate core user id', { coreUserId, error });
return next(error); return next(error);
} }
} }
async populateCoreUserId (req, res, next, coreUserId) { async populateLocalUsername (req, res, next, username) {
const { coreNode: coreNodeService } = this.dtp.services; const { user: userService } = this.dtp.services;
try { try {
coreUserId = mongoose.Types.ObjectId(coreUserId); res.locals.username = userService.filterUsername(username);
res.locals.userProfileId = await userService.getLocalUserId(res.locals.username);
if (!res.locals.userProfileId) {
throw new SiteError(404, 'Local member not found');
}
if (req.user && (req.user.type === 'User') && req.user._id.equals(res.locals.userProfileId)) {
res.locals.userProfile = await userService.getLocalUserAccount(res.locals.userProfileId);
} else {
res.locals.userProfile = await userService.getLocalUserProfile(res.locals.userProfileId);
}
return next();
} catch (error) { } catch (error) {
return next(new SiteError(406, 'Invalid User')); this.log.error('failed to populate local username', { username, error });
return next(error);
}
} }
async populateLocalUserId (req, res, next, userId) {
const { user: userService } = this.dtp.services;
try { try {
res.locals.userProfile = await coreNodeService.getUserByLocalId(coreUserId); res.locals.userProfileId = mongoose.Types.ObjectId(userId);
if (req.user && (req.user.type === 'User') && req.user._id.equals(res.locals.userProfileId)) {
res.locals.userProfile = await userService.getLocalUserAccount(res.locals.userProfileId);
} else {
res.locals.userProfile = await userService.getLocalUserProfile(res.locals.userProfileId);
}
if (!res.locals.userProfile) {
throw new SiteError(404, 'Local member not found');
}
return next(); return next();
} catch (error) { } catch (error) {
this.log.error('failed to populate coreUserId', { coreUserId, error }); this.log.error('failed to populate local user id', { userId, error });
return next(error); return next(error);
} }
} }
@ -229,8 +267,9 @@ class UserController extends SiteController {
async postProfilePhoto (req, res) { async postProfilePhoto (req, res) {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
try { try {
const displayList = this.createDisplayList('profile-photo');
await userService.updatePhoto(req.user, req.file); await userService.updatePhoto(req.user, req.file);
const displayList = this.createDisplayList('profile-photo');
displayList.showNotification( displayList.showNotification(
'Profile photo updated successfully.', 'Profile photo updated successfully.',
'success', 'success',
@ -250,8 +289,9 @@ class UserController extends SiteController {
async postHeaderImage (req, res) { async postHeaderImage (req, res) {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
try { try {
const displayList = this.createDisplayList('header-image');
await userService.updateHeaderImage(req.user, req.file); await userService.updateHeaderImage(req.user, req.file);
const displayList = this.createDisplayList('header-image');
displayList.showNotification( displayList.showNotification(
'Header image updated successfully.', 'Header image updated successfully.',
'success', 'success',
@ -271,10 +311,9 @@ class UserController extends SiteController {
async postUpdateCoreSettings (req, res) { async postUpdateCoreSettings (req, res) {
const { coreNode: coreNodeService } = this.dtp.services; const { coreNode: coreNodeService } = this.dtp.services;
try { try {
const displayList = this.createDisplayList('app-settings');
await coreNodeService.updateUserSettings(req.user, req.body); await coreNodeService.updateUserSettings(req.user, req.body);
const displayList = this.createDisplayList('app-settings');
displayList.reload(); displayList.reload();
res.status(200).json({ success: true, displayList }); res.status(200).json({ success: true, displayList });
} catch (error) { } catch (error) {

7
app/models/user.js

@ -22,17 +22,18 @@ const {
const UserSchema = new Schema({ const UserSchema = new Schema({
created: { type: Date, default: Date.now, required: true, index: -1 }, created: { type: Date, default: Date.now, required: true, index: -1 },
email: { type: String, required: true, lowercase: true, unique: true }, email: { type: String, required: true, lowercase: true, unique: true, select: false },
username: { type: String, required: true }, username: { type: String, required: true },
username_lc: { type: String, required: true, lowercase: true, unique: true, index: 1 }, username_lc: { type: String, required: true, lowercase: true, unique: true, index: 1 },
passwordSalt: { type: String, required: true }, passwordSalt: { type: String, required: true, select: false },
password: { type: String, required: true }, password: { type: String, required: true, select: false },
displayName: { type: String }, displayName: { type: String },
bio: { type: String, maxlength: 300 }, bio: { type: String, maxlength: 300 },
picture: { picture: {
large: { type: Schema.ObjectId, ref: 'Image' }, large: { type: Schema.ObjectId, ref: 'Image' },
small: { type: Schema.ObjectId, ref: 'Image' }, small: { type: Schema.ObjectId, ref: 'Image' },
}, },
header: { type: Schema.ObjectId, ref: 'Image' },
badges: { type: [String] }, badges: { type: [String] },
flags: { type: UserFlagsSchema, select: false }, flags: { type: UserFlagsSchema, select: false },
permissions: { type: UserPermissionsSchema, select: false }, permissions: { type: UserPermissionsSchema, select: false },

15
app/services/chat.js

@ -473,7 +473,12 @@ class ChatService extends SiteService {
async createMessage (author, messageDefinition) { async createMessage (author, messageDefinition) {
const { sticker: stickerService, user: userService } = this.dtp.services; const { sticker: stickerService, user: userService } = this.dtp.services;
author = await userService.getUserAccount(author._id); this.log.alert('user record', { author });
if (author.type === 'User') {
author = await userService.getLocalUserAccount(author._id);
} else {
author = await userService.getCoreUserAccount(author._id);
}
if (!author || !author.permissions || !author.permissions.canChat) { if (!author || !author.permissions || !author.permissions.canChat) {
throw new SiteError(403, `You are not permitted to chat at all on ${this.dtp.config.site.name}`); throw new SiteError(403, `You are not permitted to chat at all on ${this.dtp.config.site.name}`);
} }
@ -744,7 +749,13 @@ class ChatService extends SiteService {
const { user: userService } = this.dtp.services; const { user: userService } = this.dtp.services;
const NOW = new Date(); const NOW = new Date();
const userCheck = await userService.getUserAccount(user._id); let userCheck;
if (user.type === '') {
userCheck = await userService.getLocalUserAccount(user._id);
} else {
userCheck = await userService.getCoreUserAccount(user._id);
}
if (!userCheck || !userCheck.permissions || !userCheck.permissions.canChat) { if (!userCheck || !userCheck.permissions || !userCheck.permissions.canChat) {
throw new SiteError(403, 'You are not permitted to chat'); throw new SiteError(403, 'You are not permitted to chat');
} }

3
app/services/session.js

@ -92,8 +92,9 @@ class SessionService extends SiteService {
delete user.stats._id; delete user.stats._id;
delete user.optIn._id; delete user.optIn._id;
break; break;
case 'local': case 'local':
user = await userService.getUserAccount(userId); user = await userService.getLocalUserAccount(userId);
user.type = 'User'; user.type = 'User';
break; break;
} }

213
app/services/user.js

@ -20,6 +20,12 @@ const uuidv4 = require('uuid').v4;
const { SiteError, SiteService } = require('../../lib/site-lib'); const { SiteError, SiteService } = require('../../lib/site-lib');
/*
* The entire concept of "get a user" is in flux right now. It's best to just
* ignore what's happening in this service right now, and focus on other
* features in the sytem.
*/
class UserService extends SiteService { class UserService extends SiteService {
constructor (dtp) { constructor (dtp) {
@ -174,7 +180,7 @@ class UserService extends SiteService {
async emailOptOut (userId, category) { async emailOptOut (userId, category) {
userId = mongoose.Types.ObjectId(userId); userId = mongoose.Types.ObjectId(userId);
const user = await this.getUserAccount(userId); const user = await this.getLocalUserAccount(userId);
if (!user) { if (!user) {
throw new SiteError(406, 'Invalid opt-out token'); throw new SiteError(406, 'Invalid opt-out token');
} }
@ -199,7 +205,6 @@ class UserService extends SiteService {
throw SiteError(403, 'Invalid user account operation'); throw SiteError(403, 'Invalid user account operation');
} }
// strip characters we don't want to allow in username
userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, ''));
const username_lc = userDefinition.username.toLowerCase(); const username_lc = userDefinition.username.toLowerCase();
@ -221,7 +226,6 @@ class UserService extends SiteService {
} }
async updateForAdmin (user, userDefinition) { async updateForAdmin (user, userDefinition) {
// strip characters we don't want to allow in username
userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, ''));
const username_lc = userDefinition.username.toLowerCase(); const username_lc = userDefinition.username.toLowerCase();
@ -265,7 +269,6 @@ class UserService extends SiteService {
const updateOp = { $set: { }, $unset: { } }; const updateOp = { $set: { }, $unset: { } };
// strip characters we don't want to allow in username
updateOp.$set.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); updateOp.$set.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, ''));
if (!updateOp.$set.username || (updateOp.$set.username.length === 0)) { if (!updateOp.$set.username || (updateOp.$set.username.length === 0)) {
throw new SiteError(400, 'Must include a username'); throw new SiteError(400, 'Must include a username');
@ -303,7 +306,7 @@ class UserService extends SiteService {
}, options); }, options);
const accountEmail = account.username.trim().toLowerCase(); const accountEmail = account.username.trim().toLowerCase();
const accountUsername = await this.filterUsername(accountEmail); const accountUsername = this.filterUsername(accountEmail);
this.log.debug('locating user record', { accountEmail, accountUsername }); this.log.debug('locating user record', { accountEmail, accountUsername });
const user = await User const user = await User
@ -393,28 +396,23 @@ class UserService extends SiteService {
); );
} }
filterUserObject (user) { async getLocalUserId (username) {
const filteredUser = { const user = await User.findOne({ username_lc: username }).select('_id').lean();
_id: user._id, if (!user) {
created: user.created, return; // undefined
displayName: user.displayName,
username: user.username,
username_lc: user.username_lc,
bio: user.bio,
flags: user.flags,
permissions: user.permissions,
picture: user.picture,
};
if (filteredUser.flags && filteredUser.flags._id) {
delete filteredUser.flags._id;
} }
if (filteredUser.permissions && filteredUser.permissions._id) { return user._id;
delete filteredUser.permissions._id;
} }
return filteredUser;
async getCoreUserId (username) {
const user = await CoreUser.findOne({ username_lc: username }).select('_id').lean();
if (!user) {
return; // undefined
}
return user._id;
} }
async getUserAccount (userId) { async getLocalUserAccount (userId) {
const user = await User const user = await User
.findById(userId) .findById(userId)
.select('+email +flags +permissions +optIn +picture') .select('+email +flags +permissions +optIn +picture')
@ -427,11 +425,47 @@ class UserService extends SiteService {
return user; return user;
} }
async getUserAccounts (pagination, username) { async getCoreUserAccount (userId) {
const user = await User
.findById(userId)
.select('+email +flags +permissions +optIn +picture')
.populate(this.populateUser)
.lean();
if (!user) {
throw new SiteError(404, 'Core member account not found');
}
user.type = 'CoreUser';
return user;
}
async getLocalUserProfile (userId) {
const user = await User
.findById(userId)
.select('+email +flags +settings')
.populate(this.populateUser)
.lean();
user.type = 'User';
return user;
}
async getCoreUserProfile (userId) {
const user = await CoreUser
.findById(userId)
.select('+core +flags +settings')
.populate(this.populateUser)
.lean();
user.type = 'CoreUser';
return user;
}
async searchLocalUserAccounts (pagination, username) {
let search = { }; let search = { };
if (username) { if (username) {
username = this.filterUsername(username);
search.username_lc = { $regex: `^${username.toLowerCase().trim()}` }; search.username_lc = { $regex: `^${username.toLowerCase().trim()}` };
} }
const users = await User const users = await User
.find(search) .find(search)
.sort({ username_lc: 1 }) .sort({ username_lc: 1 })
@ -444,60 +478,24 @@ class UserService extends SiteService {
return users.map((user) => { user.type = 'User'; return user; }); return users.map((user) => { user.type = 'User'; return user; });
} }
async getUserProfile (userId) { async searchCoreUserAccounts (pagination, username) {
let user; let search = { };
try {
userId = mongoose.Types.ObjectId(userId); // will throw if invalid format
user = User.findById(userId);
} catch (error) {
user = User.findOne({ username: userId });
}
user = await user
.select('+email +flags +settings')
.populate(this.populateUser)
.lean();
return user;
}
async getPublicProfile (type, username) {
if (!username || (typeof username !== 'string')) {
throw new SiteError(406, 'Invalid username');
}
username = username.trim().toLowerCase();
if (username.length === 0) {
throw new SiteError(406, 'Invalid username');
}
let user;
switch (type) {
case 'CoreUser':
user = await CoreUser
.findOne({ username_lc: username })
.select('_id created username username_lc displayName bio picture header core')
.populate(this.populateUser)
.lean();
if (user) {
user.type = 'CoreUser';
}
break;
case 'User': username = this.filterUsername(username);
user = await User if (username) {
.findOne({ username_lc: username }) search.username_lc = { $regex: `^${username.toLowerCase().trim()}` };
.select('_id created username username_lc displayName bio picture header')
.populate(this.populateUser)
.lean();
if (user) {
user.type = 'User';
} }
break;
default: const users = await CoreUser
throw new SiteError(400, 'Invalid user account type'); .find(search)
} .sort({ username_lc: 1 })
.select('+core +coreUserId +flags +permissions +optIn')
.skip(pagination.skip)
.limit(pagination.cpp)
.lean()
;
return user; return users.map((user) => { user.type = 'CoreUser'; return user; });
} }
async getRecent (maxCount = 3) { async getRecent (maxCount = 3) {
@ -579,10 +577,6 @@ class UserService extends SiteService {
return actions; return actions;
} }
async filterUsername (username) {
return striptags(username.trim().toLowerCase()).replace(/\W/g, '');
}
async checkUsername (username) { async checkUsername (username) {
if (!username || (typeof username !== 'string') || (username.length === 0)) { if (!username || (typeof username !== 'string') || (username.length === 0)) {
throw new SiteError(406, 'Invalid username'); throw new SiteError(406, 'Invalid username');
@ -598,6 +592,34 @@ class UserService extends SiteService {
} }
} }
filterUsername (username) {
while (username[0] === '@') {
username = username.slice(1);
}
return striptags(username.trim().toLowerCase()).replace(/\W/g, '');
}
filterUserObject (user) {
const filteredUser = {
_id: user._id,
created: user.created,
displayName: user.displayName,
username: user.username,
username_lc: user.username_lc,
bio: user.bio,
flags: user.flags,
permissions: user.permissions,
picture: user.picture,
};
if (filteredUser.flags && filteredUser.flags._id) {
delete filteredUser.flags._id;
}
if (filteredUser.permissions && filteredUser.permissions._id) {
delete filteredUser.permissions._id;
}
return filteredUser;
}
async recordProfileView (user, req) { async recordProfileView (user, req) {
const { resource: resourceService } = this.dtp.services; const { resource: resourceService } = this.dtp.services;
await resourceService.recordView(req, 'User', user._id); await resourceService.recordView(req, 'User', user._id);
@ -653,6 +675,41 @@ class UserService extends SiteService {
await User.updateOne({ _id: user._id }, { $unset: { 'picture': '' } }); await User.updateOne({ _id: user._id }, { $unset: { 'picture': '' } });
} }
async updateHeaderImage (user, file) {
const { image: imageService } = this.dtp.services;
await this.removeHeaderImage(user.header);
const images = [
{
width: 1400,
height: 400,
format: 'jpeg',
formatParameters: {
quality: 80,
},
},
];
await imageService.processImageFile(user, file, images);
await User.updateOne(
{ _id: user._id },
{
$set: {
'header': images[0].image._id,
},
},
);
}
async removeHeaderImage (user) {
const { image: imageService } = this.dtp.services;
user = await this.getUserAccount(user._id);
if (user.header) {
await imageService.deleteImage(user.header);
}
await User.updateOne({ _id: user._id }, { $unset: { 'header': '' } });
}
async blockUser (user, blockedUser) { async blockUser (user, blockedUser) {
if (user._id.equals(blockedUser._id)) { if (user._id.equals(blockedUser._id)) {
throw new SiteError(406, "You can't block yourself"); throw new SiteError(406, "You can't block yourself");

2
app/views/admin/core-user/form.pug

@ -13,7 +13,7 @@ block content
if userAccount.displayName if userAccount.displayName
.uk-text-large= userAccount.displayName .uk-text-large= userAccount.displayName
div div
a(href=`/user/${userAccount._id}`) @#{userAccount.username} a(href=`/user/${userAccount.username}`) @#{userAccount.username}
.uk-card-body .uk-card-body
.uk-margin .uk-margin

2
app/views/admin/user/form.pug

@ -20,7 +20,7 @@ block content
.uk-width-auto .uk-width-auto
a(href=`mailto:${userAccount.email}`)= userAccount.email a(href=`mailto:${userAccount.email}`)= userAccount.email
.uk-width-auto .uk-width-auto
a(href=`/user/${userAccount._id}`) @#{userAccount.username} a(href=`/user/${userAccount.username}`) @#{userAccount.username}
.uk-card-body .uk-card-body
.uk-margin .uk-margin

2
app/views/components/navbar.pug

@ -52,7 +52,7 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top
i.fas.fa-user i.fas.fa-user
span Profile span Profile
li li
a(href= user.core ? `/user/core/${user._id}/settings` : `/user/${user._id}/settings`) a(href= user.core ? `/user/core/${user.username}/settings` : `/user/${user.username}/settings`)
span.nav-item-icon span.nav-item-icon
i.fas.fa-cog i.fas.fa-cog
span Settings span Settings

2
app/views/components/off-canvas.pug

@ -48,7 +48,7 @@ mixin renderMenuItem (iconClass, label)
.uk-width-expand Profile .uk-width-expand Profile
li(class={ "uk-active": (currentView === 'user-settings') }) li(class={ "uk-active": (currentView === 'user-settings') })
a(href=`/user/${user._id}/settings`).uk-display-block a(href=`/user/${user.username}/settings`).uk-display-block
div(uk-grid).uk-grid-collapse div(uk-grid).uk-grid-collapse
.uk-width-auto .uk-width-auto
.app-menu-icon .app-menu-icon

10
lib/site-ioserver.js

@ -15,12 +15,12 @@ const ConnectToken = mongoose.model('ConnectToken');
const marked = require('marked'); const marked = require('marked');
const { SiteLog } = require(path.join(__dirname, 'site-log')); const { SiteLog } = require(path.join(__dirname, 'site-log'));
const { SiteCommon } = require(path.join(__dirname, 'site-common'));
const Events = require('events'); class SiteIoServer extends SiteCommon {
class SiteIoServer extends Events {
constructor (dtp) { constructor (dtp) {
super(); super(dtp, { name: 'ioServer', slug: 'io-server' });
this.dtp = dtp; this.dtp = dtp;
this.log = new SiteLog(dtp, DTP_COMPONENT); this.log = new SiteLog(dtp, DTP_COMPONENT);
} }
@ -74,6 +74,10 @@ class SiteIoServer extends Events {
} }
async stop ( ) { async stop ( ) {
if (this.io) {
this.io.close();
delete this.io;
}
} }

Loading…
Cancel
Save