Compare commits

...

55 Commits

Author SHA1 Message Date
Andrew ee6224202d Merge branch 'master' of git.digitaltelepresence.com:digital-telepresence/dtp-sites
11 months ago
rob 228169f4b6 v0.7.1
11 months ago
rob 72d25eef27 Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-base into develop
11 months ago
rob b529e12a77 v0.8.4
11 months ago
rob 7b8ecf44b4 added inputs for username and display name in Local User Admin
11 months ago
rob 4da1c1a921 v0.7.0
11 months ago
rob 1cd6662ea0 adapting to latest DTP Base requirements
11 months ago
rob e25451ad81 Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-base into develop
11 months ago
rob 6b13e740ac adapting to latest DTP Base requirements
11 months ago
rob dcf2580d6e v0.8.3
11 months ago
rob 8ef865e691 SiteError dep
11 months ago
rob b03e8bce45 Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-base into develop
11 months ago
rob b6bec9f0f2 Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-sites into develop
11 months ago
rob c4d9950762 v0.8.2
11 months ago
rob a06d9630ff config update
11 months ago
rob 2a3888976e v0.8.1
11 months ago
rob beb8fbb451 MONGODB_HOST can specify credentials
11 months ago
rob 7fac164758 v0.8.0
11 months ago
rob 814c358233 ban user button in Admin
11 months ago
rob d5eb929cc2 integrating latest base and Logan; and user ban service
11 months ago
rob 4dfa167d53 v0.7.6
11 months ago
rob bd3d913484 resourceId management
11 months ago
rob 7d707c58b5 v0.7.5
11 months ago
rob 53753a04fb user session management
11 months ago
rob 5fcfe9f112 refactor
11 months ago
rob 3c40f55468 Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-base into develop
11 months ago
rob 022f674e36 v0.7.4
11 months ago
rob 71c6440836 comment updates
11 months ago
rob 5e58e98bd8 fix vuln for elevated privileges at create
11 months ago
rob 3d4f9a1c21 added options for multer
12 months ago
rob 0125add6f2 added a basic markdown block to clean up marked output
12 months ago
rob 9a06b424c5 clean up the shutdown of webapp
1 year ago
rob df1a10c25f log controller names while loading them
1 year ago
rob 620581ada3 v0.7.3
1 year ago
rob af5b8aa5cf update local user admin
1 year ago
rob 4a62a37962 v0.7.2
1 year ago
rob 8548919a84 added a user not found error for permission grants
1 year ago
rob 98f07769d6 v0.7.1
1 year ago
rob e96233d001 change model loaders to accept the Mongoose connection instead of using global
1 year ago
rob 857acbb980 v0.7.0
1 year ago
rob bbea5551c2 Merge branch 'develop' of git.digitaltelepresence.com:digital-telepresence/dtp-base into develop
1 year ago
rob 98853ced33 Core vs. Local user updates (there will be more)
1 year ago
CyberShell 9ae1135785 Merge pull request 'Changes to menu rendering and small touch-ups' (#40) from pagesUpdates into develop
1 year ago
Andrew 5696681288 Changes to menu rendering and small touch-ups
1 year ago
CyberShell d29ea133fc Merge pull request 'add explicit authentication for Mongo when in prod' (#39) from mongoAuthExplicit into develop
1 year ago
Andrew d4f2b3b9ec Merge branch 'develop' into mongoAuthExplicit
1 year ago
Andrew c7f58dc8f2 add explict authentication when running in production
1 year ago
Andrew 6b67ada93f fix url
1 year ago
CyberShell eef39d4c2a Merge pull request 'changes' (#38) from changes into develop
1 year ago
Andrew cde9f78759 add bio to author view
1 year ago
Andrew c73af4a79c fix post view
1 year ago
Andrew b1a5bc7ff1 fix post view
1 year ago
Andrew 57ae7c4091 Fix in postController
1 year ago
Andrew d4839e2d3b Author view
1 year ago
Andrew ab7aa081cc Added a lot...
1 year ago

@ -62,6 +62,9 @@ MAILGUN_DOMAIN=
MONGODB_HOST=localhost:27017
MONGODB_DATABASE=dtp-sites
MONGODB_USERNAME=mongo-user
MONGODB_PASSWORD=change-me!
MONGODB_OPTIONS=
#
# Redis configuration

@ -12,6 +12,15 @@ The only qualified operated system for hosting a DTP Sites suite is [Ubuntu 20.0
apt-get -y install build-essential python3-pip
```
## 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
You will need MongoDB and MinIO installed and running before you can start DTP Sites web services.

@ -81,6 +81,7 @@ class AdminController extends SiteController {
coreNode: coreNodeService,
dashboard: dashboardService,
venue: venueService,
logan: loganService,
} = this.dtp.services;
res.locals.stats = {
@ -92,6 +93,11 @@ class AdminController extends SiteController {
res.locals.channels = await venueService.getChannels();
res.locals.pageTitle = `Admin Dashbord for ${this.dtp.config.site.name}`;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getHomeView',
});
res.render('admin/index');
}
}
@ -99,5 +105,6 @@ class AdminController extends SiteController {
module.exports = {
slug: 'admin',
name: 'admin',
className: 'AdminController',
create: async (dtp) => { return new AdminController(dtp); },
};

@ -38,31 +38,76 @@ class AnnouncementAdminController extends SiteController {
}
async populateAnnouncementId (req, res, next, announcementId) {
const { announcement: announcementService } = this.dtp.services;
const {
announcement: announcementService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.announcement = await announcementService.getById(announcementId);
return next();
} catch (error) {
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateAnnouncementId',
message: `failed to populate announcement: ${error.message}`,
data: { announcementId, error },
});
return next(error);
}
}
async postUpdateAnnouncement (req, res, next) {
const { announcement: announcementService } = this.dtp.services;
const {
announcement: announcementService,
logan: loganService,
} = this.dtp.services;
try {
await announcementService.update(res.locals.announcement, req.body);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postUpdateAnnouncement',
message: 'announcement updated',
data: {
announcement: {
_id: res.locals.announcement._id,
},
},
});
res.redirect('/admin/announcement');
} catch (error) {
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postUpdateAnnouncement',
message: `failed to update announcement: ${error.message}`,
data: { error },
});
return next(error);
}
}
async postCreateAnnouncement (req, res, next) {
const { announcement: announcementService } = this.dtp.services;
const {
announcement: announcementService,
logan: loganService,
} = this.dtp.services;
try {
await announcementService.create(req.body);
res.locals.announcement = await announcementService.create(req.body);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postCreateAnnouncement',
message: 'announcement created',
data: {
announcement: res.locals.announcement,
},
});
res.redirect('/admin/announcement');
} catch (error) {
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postCreateAnnouncement',
message: `failed to create announcement: ${error.message}`,
data: { error },
});
return next(error);
}
}
@ -83,14 +128,27 @@ class AnnouncementAdminController extends SiteController {
}
async deleteAnnouncement (req, res) {
const { announcement: announcementService } = this.dtp.services;
const {
announcement: announcementService,
logan: loganService,
} = this.dtp.services;
try {
const displayList = this.createDisplayList('delete-announcement');
await announcementService.remove(res.locals.announcement);
displayList.reload();
res.status(200).json({ success: true, displayList });
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'deleteAnnouncement',
data: { announcement: { _id: res.locals.announcement._id } },
});
} catch (error) {
this.log.error('failed to delete announcement', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'deleteAnnouncement',
message: `failed to delete announcement: ${error.message}`,
data: { error },
});
res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -102,8 +160,6 @@ class AnnouncementAdminController extends SiteController {
module.exports = {
name: 'announcement',
slug: 'announcement',
create: async (dtp) => {
let controller = new AnnouncementAdminController(dtp);
return controller;
},
className: 'AnnouncementAdminController',
create: async (dtp) => { return new AnnouncementAdminController(dtp); },
};

@ -8,7 +8,7 @@ const express = require('express');
const { SiteController } = require('../../../lib/site-lib');
class ContentReportController extends SiteController {
class ContentReportAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -89,5 +89,6 @@ class ContentReportController extends SiteController {
module.exports = {
name: 'adminContentReport',
slug: 'admin-content-report',
create: async (dtp) => { return new ContentReportController(dtp); },
className: 'ContentReportAdminController',
create: async (dtp) => { return new ContentReportAdminController(dtp); },
};

@ -9,7 +9,7 @@ const express = require('express');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class CoreNodeController extends SiteController {
class CoreNodeAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -142,5 +142,6 @@ class CoreNodeController extends SiteController {
module.exports = {
name: 'adminCoreNode',
slug: 'admin-core-node',
create: async (dtp) => { return new CoreNodeController(dtp); },
className: 'CoreNodeAdminController',
create: async (dtp) => { return new CoreNodeAdminController(dtp); },
};

@ -9,7 +9,7 @@ const express = require('express');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class CoreUserController extends SiteController {
class CoreUserAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -90,5 +90,6 @@ class CoreUserController extends SiteController {
module.exports = {
name: 'adminCoreUser',
slug: 'admin-core-user',
create: async (dtp) => { return new CoreUserController(dtp); },
className: 'CoreUserAdminController',
create: async (dtp) => { return new CoreUserAdminController(dtp); },
};

@ -12,7 +12,7 @@ const NetHostStats = mongoose.model('NetHostStats');
const { /*SiteError,*/ SiteController } = require('../../../lib/site-lib');
class HostController extends SiteController {
class HostAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -118,5 +118,6 @@ class HostController extends SiteController {
module.exports = {
name: 'adminHost',
slug: 'admin-host',
create: async (dtp) => { return new HostController(dtp); },
className: 'HostAdminController',
create: async (dtp) => { return new HostAdminController(dtp); },
};

@ -8,7 +8,7 @@ const express = require('express');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class JobQueueController extends SiteController {
class JobQueueAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -121,5 +121,6 @@ class JobQueueController extends SiteController {
module.exports = {
name: 'adminJobQueue',
slug: 'admin-job-queue',
create: async (dtp) => { return new JobQueueController(dtp); },
className: 'JobQueueAdminController',
create: async (dtp) => { return new JobQueueAdminController(dtp); },
};

@ -8,7 +8,7 @@ const express = require('express');
const { SiteController } = require('../../../lib/site-lib');
class LogController extends SiteController {
class LogAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -53,5 +53,6 @@ class LogController extends SiteController {
module.exports = {
name: 'adminLog',
slug: 'admin-log',
create: async (dtp) => { return new LogController(dtp); },
className: 'LogAdminController',
create: async (dtp) => { return new LogAdminController(dtp); },
};

@ -8,7 +8,7 @@ const express = require('express');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class NewsletterController extends SiteController {
class NewsletterAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -169,8 +169,6 @@ class NewsletterController extends SiteController {
module.exports = {
name: 'adminNewsletter',
slug: 'admin-newsletter',
create: async (dtp) => {
let controller = new NewsletterController(dtp);
return controller;
},
className: 'NewsletterAdminController',
create: async (dtp) => { return new NewsletterAdminController(dtp); },
};

@ -160,5 +160,6 @@ class NewsroomAdminController extends SiteController {
module.exports = {
name: 'newsroomAdmin',
slug: 'newsroom-admin',
className: 'NewsroomAdminController',
create: async (dtp) => { return new NewsroomAdminController(dtp); },
};

@ -51,5 +51,6 @@ class OtpAdminController extends SiteController {
module.exports = {
name: 'adminOtp',
slug: 'admin-opt',
className: 'OtpAdminController',
create: async (dtp) => { return new OtpAdminController(dtp); },
};

@ -40,10 +40,12 @@ class PageController extends SiteController {
async populatePageId (req, res, next, pageId) {
const { page: pageService } = this.dtp.services;
try {
res.locals.page = await pageService.getById(pageId);
res.locals.page = await pageService.getById(pageId, true);
if (!res.locals.page) {
throw new SiteError(404, 'Page not found');
}
res.locals.isParentPage = await pageService.isParentPage(pageId);
return next();
} catch (error) {
this.log.error('failed to populate pageId', { pageId, error });
@ -131,5 +133,6 @@ class PageController extends SiteController {
module.exports = {
name: 'adminPage',
slug: 'admin-page',
className: 'PageController',
create: async (dtp) => { return new PageController(dtp); },
};

@ -101,7 +101,9 @@ class PostController extends SiteController {
try {
res.locals.pageTitle = `Posts for ${this.dtp.config.site.name}`;
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.posts = await postService.getAllPosts(res.locals.pagination);
const {posts, totalPostCount} = await postService.getAllPosts(res.locals.pagination);
res.locals.posts = posts;
res.locals.totalPostCount = totalPostCount;
res.render('admin/post/index');
} catch (error) {
this.log.error('failed to fetch posts', { error });
@ -140,5 +142,6 @@ class PostController extends SiteController {
module.exports = {
name: 'adminPost',
slug: 'admin-post',
className: 'PostController',
create: async (dtp) => { return new PostController(dtp); },
};

@ -8,7 +8,7 @@ const express = require('express');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class ServiceNodeController extends SiteController {
class ServiceNodeAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -130,5 +130,6 @@ class ServiceNodeController extends SiteController {
module.exports = {
name: 'adminServiceNode',
slug: 'admin-service-node',
create: async (dtp) => { return new ServiceNodeController(dtp); },
className: 'ServiceNodeAdminController',
create: async (dtp) => { return new ServiceNodeAdminController(dtp); },
};

@ -8,7 +8,7 @@ const express = require('express');
const { SiteController } = require('../../../lib/site-lib');
class SettingsController extends SiteController {
class SettingsAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -127,5 +127,6 @@ class SettingsController extends SiteController {
module.exports = {
name: 'adminSettings',
slug: 'admin-settings',
create: async (dtp) => { return new SettingsController(dtp); },
className: 'SettingsAdminController',
create: async (dtp) => { return new SettingsAdminController(dtp); },
};

@ -105,5 +105,6 @@ class SiteLinkAdminController extends SiteController {
module.exports = {
name: 'adminSiteLink',
slug: 'admin-site-link',
className: 'SiteLinkAdminController',
create: async (dtp) => { return new SiteLinkAdminController(dtp); },
};

@ -6,9 +6,9 @@
const express = require('express');
const { SiteController } = require('../../../lib/site-lib');
const { SiteController, SiteError } = require('../../../lib/site-lib');
class UserController extends SiteController {
class UserAdminController extends SiteController {
constructor (dtp) {
super(dtp, module.exports);
@ -22,37 +22,73 @@ class UserController extends SiteController {
return next();
});
router.param('userId', this.populateUserId.bind(this));
router.param('localUserId', this.populateLocalUserId.bind(this));
router.post('/local/:localUserId', this.postUpdateLocalUser.bind(this));
router.get('/local/:localUserId', this.getLocalUserView.bind(this));
router.post('/:userId', this.postUpdateUser.bind(this));
router.get('/:userId', this.getUserView.bind(this));
router.get('/', this.getHomeView.bind(this));
return router;
}
async populateUserId (req, res, next, userId) {
async populateLocalUserId (req, res, next, localUserId) {
const { user: userService } = this.dtp.services;
try {
res.locals.userAccount = await userService.getUserAccount(userId);
res.locals.userAccount = await userService.getLocalUserAccount(localUserId);
if (!res.locals.userAccount) {
throw new SiteError(404, 'User not found');
}
return next();
} catch (error) {
return next(error);
}
}
async postUpdateUser (req, res, next) {
const { user: userService } = this.dtp.services;
async postUpdateLocalUser (req, res, next) {
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
await userService.updateForAdmin(res.locals.userAccount, req.body);
this.log.debug('local user update', { action: req.body.action });
switch (req.body.action) {
case 'update':
await userService.updateLocalForAdmin(res.locals.userAccount, req.body);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postUpdateLocalUser',
message: 'local user account updated',
data: {
userAccount: {
_id: res.locals.userAccount._id,
username: res.locals.userAccount.username,
},
},
});
break;
case 'ban':
await userService.ban(res.locals.userAccount);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postUpdateLocalUser',
message: 'local user banned from the app',
data: {
userAccount: {
_id: res.locals.userAccount._id,
username: res.locals.userAccount.username,
},
},
});
break;
}
res.redirect('/admin/user');
} catch (error) {
return next(error);
}
}
async getUserView (req, res, next) {
async getLocalUserView (req, res, next) {
const { comment: commentService } = this.dtp.services;
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
@ -68,7 +104,7 @@ class UserController extends SiteController {
const { user: userService } = this.dtp.services;
try {
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.render('admin/user/index');
} catch (error) {
@ -80,5 +116,6 @@ class UserController extends SiteController {
module.exports = {
name: 'adminUser',
slug: 'admin-user',
create: async (dtp) => { return new UserController(dtp); },
className: 'UserAdminController',
create: async (dtp) => { return new UserAdminController(dtp); },
};

@ -138,5 +138,6 @@ class VenueAdminController extends SiteController {
module.exports = {
name: 'adminVenue',
slug: 'admin-venue',
className: 'VenueAdminController',
create: async (dtp) => { return new VenueAdminController(dtp); },
};

@ -78,8 +78,6 @@ class AnnouncementController extends SiteController {
module.exports = {
slug: 'announcement',
name: 'announcement',
create: async (dtp) => {
let controller = new AnnouncementController(dtp);
return controller;
},
className: 'AnnouncementController',
create: async (dtp) => { return new AnnouncementController(dtp); },
};

@ -84,7 +84,10 @@ class AuthController extends SiteController {
}
async postOtpEnable (req, res, next) {
const { otpAuth: otpAuthService } = this.dtp.services;
const {
logan: loganService,
otpAuth: otpAuthService,
} = this.dtp.services;
const service = req.body['otp-service'];
const secret = req.body['otp-secret'];
@ -95,17 +98,33 @@ class AuthController extends SiteController {
this.log.info('enabling OTP protections', { service, secret, token });
res.locals.otpAccount = await otpAuthService.createOtpAccount(req, service, secret, token);
res.locals.otpRedirectURL = otpRedirectURL;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postOtpEnable',
data: {
user: {
_id: req.user._id,
username: req.user.username,
},
},
});
res.render('otp/new-account');
} catch (error) {
this.log.error('failed to enable OTP protections', {
service, error,
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postOtpEnable',
message: `failed to enable OTP account: ${error.message}`,
data: { service, error },
});
return next(error);
}
}
async postOtpAuthenticate (req, res, next) {
const { otpAuth: otpAuthService } = this.dtp.services;
const {
logan: loganService,
otpAuth: otpAuthService,
} = this.dtp.services;
if (!req.user) {
return res.status(403).json({
@ -129,36 +148,87 @@ class AuthController extends SiteController {
}
try {
await otpAuthService.startOtpSession(req, service, passcode);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postOtpAuthenticate',
data: {
user: {
_id: req.user._id,
username: req.user.username,
},
},
});
return res.redirect(req.body['otp-redirect']);
} catch (error) {
this.log.error('failed to verify one-time password for 2FA', {
service, error,
}, req.user);
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postOtpAuthenticate',
message: `failed to verify one-time password: ${error.message}`,
data: {
user: {
_id: req.user._id,
username: req.user.username,
},
error,
},
});
return next(error);
}
}
async postLogin (req, res, next) {
const { logan: loganService } = this.dtp.services;
const redirectUri = req.session.loginReturnTo || '/';
this.log.debug('starting passport.authenticate', { redirectUri });
passport.authenticate('dtp-local', (error, user/*, info*/) => {
if (error) {
req.session.loginResult = error.toString();
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postLogin',
message: `login failed: ${error.message}`,
data: { error },
});
return next(error);
}
if (!user) {
req.session.loginResult = 'Username or email address is unknown.';
loganService.sendRequestEvent(module.exports, req, {
level: 'alert',
event: 'postLogin',
message: 'username or email address is unknown',
});
return res.redirect('/welcome/login');
}
this.log.info('user logging in', { user: user.username });
req.login(user, async (error) => {
if (error) {
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postLogin',
message: `failed to start user session: ${error.message}`,
data: { error },
});
return next(error);
}
// scrub login return URL from session
delete req.session.loginReturnTo;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postLogin',
message: 'session started for site member',
data: {
user: {
_id: user._id,
username: user.username,
},
},
});
// redirect to whatever was wanted
return res.redirect(redirectUri);
});
@ -166,20 +236,29 @@ class AuthController extends SiteController {
}
async getPersonalApiToken (req, res, next) {
const {
apiGuard: apiGuardService,
logan: loganService,
} = this.dtp.platform.services;
try {
const { apiGuard: apiGuardService } = this.dtp.platform.services;
res.locals.apiToken = await apiGuardService.createApiToken(req.user, [
'account-read',
// additional scopes go here
]);
res.render('api-token/view');
} catch (error) {
this.log.error('failed to generate API token', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getPersonalApiToken',
message: `failed to generate API token: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getSocketToken (req, res, next) {
const { logan: loganService } = this.dtp.services;
try {
const token = await ConnectToken.create({
created: new Date(),
@ -192,35 +271,68 @@ class AuthController extends SiteController {
token: token.token
});
} catch (error) {
this.log.error('failed to create Socket.io connect token', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getSocketToken',
message: `failed to create socket token: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getCoreHome (req, res, next) {
const { coreNode: coreNodeService } = this.dtp.services;
const {
coreNode: coreNodeService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.currentView = 'welcome';
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.connectedCores = await coreNodeService.getConnectedCores(res.locals.pagination);
res.render('welcome/core-home');
} catch (error) {
this.log.error('failed to generate Core auth menu', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getCoreHome',
message: `failed to render view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getLogout (req, res, next) {
const { logan: loganService } = this.dtp.services;
if (!req.user) {
return next(new SiteError(403, 'You are not signed in'));
}
const user = {
_id: req.user._id,
username: req.user.username,
};
req.logout();
req.session.destroy((err) => {
if (err) {
this.log.error('failed to destroy browser session', { err });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getLogout',
message: 'failed to destroy browser session',
data: { error: err },
});
return next(err);
}
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getLogout',
message: 'user terminated their browser session',
data: { user },
});
res.redirect('/');
});
}
@ -229,5 +341,6 @@ class AuthController extends SiteController {
module.exports = {
slug: 'auth',
name: 'auth',
className: 'AuthController',
create: async (dtp) => { return new AuthController(dtp); },
};
};

@ -26,15 +26,11 @@ class AuthorController extends SiteController {
return res.redirect(302, '/welcome');
}
if (req.user.flags.isAdmin) {
return next();
}
const canAuthor = req.user.permissions.canAuthorPosts;
const canPublish = req.user.permissions.canPublishPosts;
if (!canAuthor && !canPublish) {
return next(new SiteError(403, 'Author privileges are required'));
return next(new SiteError(403, 'Author or Publisher privileges are required'));
}
return next();
}
@ -69,29 +65,35 @@ class AuthorController extends SiteController {
async getPublishedPostHome (req, res, next) {
const { post: postService } = this.dtp.services;
try {
const isAdmin = req.user.flags.isAdmin;
const canAuthor = req.user.permissions.canAuthorPosts;
const canPublish = req.user.permissions.canPublishPosts;
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.published = { };
if(canAuthor) {
if ( canPublish ) {
res.locals.published = await postService.getPosts( { skip: 0, cpp: 5 }, ['published'], true );
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, ['published'], true );
res.locals.published.posts = posts;
res.locals.published.posts.totalPostCount = totalPostCount;
res.locals.allPosts = true;
res.locals.published.all = true;
res.locals.published.posts.all = true;
} else {
res.locals.published = await postService.getForAuthor( req.user, ['published'], { skip: 0, cpp: 5 } );
res.locals.published = await postService.getForAuthor( req.user, ['published'], res.locals.pagination );
}
}
else if ( canPublish || isAdmin ) {
res.locals.published = await postService.getPosts( { skip: 0, cpp: 5 }, ['published'], true );
else if ( canPublish) {
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, ['published'], true );
res.locals.published.posts = posts;
res.locals.published.posts.totalPostCount = totalPostCount;
res.locals.published.all = true;
}
@ -105,31 +107,37 @@ class AuthorController extends SiteController {
async getDraftsHome (req, res, next) {
const { post: postService } = this.dtp.services;
try {
const isAdmin = req.user.flags.isAdmin;
const canAuthor = req.user.permissions.canAuthorPosts;
const canPublish = req.user.permissions.canPublishPosts;
res.locals.pagination = this.getPaginationParameters(req, 20);
const status = ['draft'];
res.locals.drafts = { };
if(canAuthor) {
if ( canPublish ) {
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, status, true );
res.locals.drafts = await postService.getPosts( { skip: 0, cpp: 5 }, status, true );
res.locals.drafts.posts = posts;
res.locals.drafts.posts.totalPostCount = totalPostCount;
res.locals.allPosts = true;
res.locals.drafts.all = true;
res.locals.drafts.posts.all = true;
} else {
res.locals.drafts = await postService.getForAuthor( req.user, status, { skip: 0, cpp: 5 } );
res.locals.drafts = await postService.getForAuthor( req.user, status, res.locals.pagination );
}
}
else if ( canPublish || isAdmin ) {
res.locals.drafts = await postService.getPosts( { skip: 0, cpp: 5 }, status, true );
res.locals.drafts.all = true;
else if ( canPublish) {
const {posts, totalPostCount }= await postService.getPosts( res.locals.pagination, status, true );
res.locals.drafts.posts = posts;
res.locals.drafts.posts.totalPostCount = totalPostCount;
res.locals.allPosts = true;
res.locals.drafts.posts.all = true;
}
res.render('author/draft/index');

@ -132,7 +132,10 @@ class ChatController extends SiteController {
}
async populateRoomId (req, res, next, roomId) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.room = await chatService.getRoomById(roomId);
if (!res.locals.room) {
@ -141,12 +144,21 @@ class ChatController extends SiteController {
return next();
} catch (error) {
this.log.error('failed to populate roomId', { roomId, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateRoomId',
message: error.message,
data: { roomId, error },
});
return next(error);
}
}
async populateInviteId (req, res, next, inviteId) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.invite = await chatService.getRoomInviteById(inviteId);
if (!res.locals.invite) {
@ -155,12 +167,21 @@ class ChatController extends SiteController {
return next();
} catch (error) {
this.log.error('failed to populate inviteId', { inviteId, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateInviteId',
message: error.message,
data: { inviteId, error },
});
return next(error);
}
}
async postRoomInviteAction (req, res) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
const { response } = req.body;
const displayList = this.createDisplayList('room-invite-action');
@ -168,11 +189,27 @@ class ChatController extends SiteController {
switch (response) {
case 'accept':
await chatService.acceptRoomInvite(res.locals.invite);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postRoomInviteAction',
message: 'invitation accepted successfully',
data: {
invite: res.locals.invite,
},
});
displayList.navigateTo(`/chat/room/${res.locals.invite.room._id}`);
break;
case 'reject':
await chatService.rejectRoomInvite(res.locals.invite);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postRoomInviteAction',
message: 'invitation rejected successfully',
data: {
invite: res.locals.invite,
},
});
displayList.showNotification(
`Chat room invite rejected`,
'success',
@ -192,6 +229,12 @@ class ChatController extends SiteController {
response: req.body.response,
error,
});
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postRoomInviteAction',
message: `failed to execute room invite action: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -200,7 +243,11 @@ class ChatController extends SiteController {
}
async postRoomInvite (req, res) {
const { chat: chatService, user: userService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
user: userService,
} = this.dtp.services;
this.log.debug('room invite received', { invite: req.body });
if (!req.body.username || !req.body.username.length) {
return res.status(400).json({ success: false, message: 'Please provide a username' });
@ -223,7 +270,7 @@ class ChatController extends SiteController {
throw new SiteError(400, "You can't invite yourself.");
}
await chatService.sendRoomInvite(res.locals.room, member, req.body);
res.locals.invite = await chatService.sendRoomInvite(res.locals.room, member, req.body);
const displayList = this.createDisplayList('invite create');
displayList.showNotification(
@ -232,9 +279,28 @@ class ChatController extends SiteController {
'top-left',
5000,
);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postRoomInvite',
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
invite: res.locals.invite,
},
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to create room invitation', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postRoomInvite',
message: `failed to create room invitation: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -243,35 +309,78 @@ class ChatController extends SiteController {
}
async postRoomUpdate (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.room = await chatService.updateRoom(res.locals.room, req.body);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postRoomUpdate',
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
},
});
res.redirect(`/chat/room/${res.locals.room._id}`);
} catch (error) {
this.log.error('failed to update chat room', {
// roomId: res.locals.room._id,
error,
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postRoomUpdate',
message: `failed to update chat room: ${error.message}`,
data: { error },
});
return next(error);
}
}
async postRoomCreate (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.room = await chatService.createRoom(req.user, req.body);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postRoomCreate',
message: 'chat room created',
data: {
room: res.locals.room,
},
});
res.redirect(`/chat/room/${res.locals.room._id}`);
} catch (error) {
this.log.error('failed to create chat room', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postRoomCreate',
message: `failed to create chat room: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getRoomEditor (req, res) {
const { logan: loganService } = this.dtp.services;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomEditor',
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
},
});
res.render('chat/room/editor');
}
async getRoomForm (req, res, next) {
const { logan: loganService } = this.dtp.services;
const validFormNames = [
'invite-member',
];
@ -280,65 +389,148 @@ class ChatController extends SiteController {
return next(new SiteError(404, 'Form not found'));
}
try {
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomForm',
data: { formName },
});
res.render(`chat/room/form/${formName}`);
} catch (error) {
this.log.error('failed to render form', { formName, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getRoomForm',
message: `failed to render form: ${error.message}`,
data: { formName, error },
});
return next(error);
}
}
async getRoomInviteView (req, res) {
const { logan: loganService } = this.dtp.services;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomInviteView',
data: {
invite: res.locals.invite,
},
});
res.render('chat/room/invite/view');
}
async getRoomInviteHome (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.invites = {
new: await chatService.getRoomInvites(res.locals.room, 'new'),
accepted: await chatService.getRoomInvites(res.locals.room, 'accepted'),
rejected: await chatService.getRoomInvites(res.locals.room, 'rejected'),
};
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomInviteHome',
});
res.render('chat/room/invite');
} catch (error) {
this.log.error('failed to render the room invites view', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getRoomInviteHome',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getRoomSettings (req, res) {
const { logan: loganService } = this.dtp.services;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomSettings',
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
},
});
res.render('chat/room/editor');
}
async getRoomView (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.pageTitle = res.locals.room.name;
const pagination = { skip: 0, cpp: 20 };
res.locals.chatMessages = await chatService.getChannelHistory(res.locals.room, pagination);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomView',
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
},
});
res.render('chat/room/view');
} catch (error) {
this.log.error('failed to render chat room view', { roomId: req.params.roomId, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getRoomView',
message: `failed to render the view: ${error.message}`,
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
error,
},
});
return next(error);
}
}
async getRoomHome (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.publicRooms = await chatService.getPublicRooms(req.user, res.locals.pagination);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getRoomHome',
});
res.render('chat/room/index');
} catch (error) {
this.log.error('failed to render room home', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getRoomHome',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getHome (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.pageTitle = 'Chat Home';
@ -349,15 +541,28 @@ class ChatController extends SiteController {
res.locals.joinedChatRooms.forEach((room) => roomIds.push(room._id));
res.locals.timeline = await chatService.getMultiRoomTimeline(roomIds, res.locals.pagination);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getHome',
});
res.render('chat/index');
} catch (error) {
this.log.error('failed to render chat home', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getHome',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async deleteInvite (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
if (res.locals.room.owner._id.equals(req.user._id)) {
throw new SiteError(403, 'This is not your invitiation');
@ -374,15 +579,34 @@ class ChatController extends SiteController {
5000,
);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'deleteInvite',
message: 'room invitation deleted',
data: {
invite: {
_id: res.locals.invite._id,
},
},
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to delete chat room invite', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'deleteInvite',
message: `failed to delete chat room invite: ${error.message}`,
data: { error },
});
return next(error);
}
}
async deleteRoom (req, res, next) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
logan: loganService,
} = this.dtp.services;
try {
if (res.locals.room.owner._id.equals(req.user._id)) {
throw new SiteError(403, 'This is not your chat room');
@ -393,9 +617,26 @@ class ChatController extends SiteController {
const displayList = this.createDisplayList('delete chat invite');
displayList.navigateTo('/chat');
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'deleteRoom',
message: 'chat room deleted',
data: {
room: {
_id: res.locals.room._id,
name: res.locals.room.name,
},
},
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to delete chat room invite', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'deleteRoom',
message: `failed to delete chat room: ${error.message}`,
data: { error },
});
return next(error);
}
}
@ -404,5 +645,6 @@ class ChatController extends SiteController {
module.exports = {
slug: 'chat',
name: 'chat',
className: 'ChatController',
create: async (dtp) => { return new ChatController(dtp); },
};

@ -153,5 +153,6 @@ class CommentController extends SiteController {
module.exports = {
slug: 'comment',
name: 'comment',
className: 'CommentController',
create: async (dtp) => { return new CommentController(dtp); },
};

@ -38,27 +38,43 @@ class EmailController extends SiteController {
}
async getEmailOptOut (req, res, next) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
await userService.emailOptOut(req.query.u, req.query.c);
res.render('email/opt-out-success');
} catch (error) {
this.log.error('failed to opt-out from email', {
userId: req.query.t,
category: req.query.c,
error,
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getEmailOptOut',
message: 'failed to opt-out from email',
data: {
userId: req.query.u,
category: req.query.c,
error,
},
});
return next(error);
}
}
async getEmailVerify (req, res, next) {
const { email: emailService } = this.dtp.services;
const {
email: emailService,
logan: loganService,
} = this.dtp.services;
try {
await emailService.verifyToken(req.query.t);
res.render('email/verify-success');
} catch (error) {
this.log.error('failed to verify email', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getEmailVerify',
message: `failed to verify email: ${error.message}`,
data: { error },
});
return next(error);
}
}
@ -67,5 +83,6 @@ class EmailController extends SiteController {
module.exports = {
slug: 'email',
name: 'email',
className: 'EmailController',
create: async (dtp) => { return new EmailController(dtp); },
};

@ -66,5 +66,6 @@ class FormController extends SiteController {
module.exports = {
slug: 'form',
name: 'form',
className: 'FormController',
create: async (dtp) => { return new FormController(dtp); },
};

@ -59,5 +59,6 @@ class HiveController extends SiteController {
module.exports = {
slug: 'hive',
name: 'hive',
className: 'HiveController',
create: async (dtp) => { return new HiveController(dtp); },
};

@ -95,5 +95,6 @@ class HiveKaleidoscopeController extends SiteController {
module.exports = {
name: 'hiveKaleidoscope',
slug: 'hive-kaleidoscope',
className: 'HiveKaleidoscopeController',
create: async (dtp) => { return new HiveKaleidoscopeController(dtp); },
};

@ -78,7 +78,7 @@ class HiveUserController extends SiteController {
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.userProfiles = await userService.getUserAccounts(res.locals.pagination, res.locals.q);
res.locals.userProfiles = res.locals.userProfiles.map((user) => {
@ -151,8 +151,6 @@ class HiveUserController extends SiteController {
module.exports = {
name: 'hiveUser',
slug: 'hive-user',
create: async (dtp) => {
let controller = new HiveUserController(dtp);
return controller;
},
className: 'HiveUserController',
create: async (dtp) => { return new HiveUserController(dtp); },
};

@ -63,7 +63,12 @@ class HomeController extends SiteController {
}
async getHome (req, res, next) {
const { announcement: announcementService, hive: hiveService, post: postService } = this.dtp.services;
const {
announcement: announcementService,
hive: hiveService,
logan: loganService,
post: postService,
} = this.dtp.services;
try {
res.locals.announcements = await announcementService.getLatest(req.user);
res.locals.featuredPosts = await postService.getFeaturedPosts(3);
@ -72,17 +77,28 @@ class HomeController extends SiteController {
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.posts = await postService.getPosts(res.locals.pagination);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getHome',
});
res.render('index');
} catch (error) {
this.log.error('failed to render home view', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getHome',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
}
module.exports = {
isHome: true,
slug: 'home',
name: 'home',
isHome: true,
className: 'HomeController',
create: async (dtp) => { return new HomeController(dtp); },
};

@ -134,5 +134,6 @@ class ImageController extends SiteController {
module.exports = {
slug: 'image',
name: 'image',
className: 'ImageController',
create: async (dtp) => { return new ImageController(dtp); },
};

@ -66,5 +66,6 @@ class ManifestController extends SiteController {
module.exports = {
slug: 'manifest',
name: 'manifest',
className: 'ManifestController',
create: async (dtp) => { return new ManifestController(dtp); },
};

@ -94,8 +94,6 @@ class NewsletterController extends SiteController {
module.exports = {
slug: 'newsletter',
name: 'newsletter',
create: async (dtp) => {
let controller = new NewsletterController(dtp);
return controller;
},
className: 'NewsletterController',
create: async (dtp) => { return new NewsletterController(dtp); },
};

@ -81,5 +81,6 @@ class NewsroomController extends SiteController {
module.exports = {
slug: 'newsroom',
name: 'newsroom',
className: 'NewsroomController',
create: (dtp) => { return new NewsroomController(dtp); },
};

@ -74,5 +74,6 @@ class NotificationController extends SiteController {
module.exports = {
slug: 'notification',
name: 'notification',
className: 'NotificationController',
create: async (dtp) => { return new NotificationController(dtp); },
};

@ -65,8 +65,6 @@ class PageController extends SiteController {
module.exports = {
slug: 'page',
name: 'page',
create: async (dtp) => {
let controller = new PageController(dtp);
return controller;
},
className: 'PageController',
create: async (dtp) => { return new PageController(dtp); },
};

@ -33,8 +33,8 @@ class PostController extends SiteController {
if (req.user && req.user.flags.isAdmin) {
return next();
}
if (!req.user || !req.flags.isAdmin) {
return next(new SiteError(403, 'Author or admin privileges are required'));
if (!req.user || !req.user.permissions.canAuthorPages) {
return next(new SiteError(403, 'Author privileges are required'));
}
return next();
}
@ -43,11 +43,12 @@ class PostController extends SiteController {
res.locals.currentView = 'home';
return next();
});
router.param('username', this.populateUsername.bind(this));
router.param('postSlug', this.populatePostSlug.bind(this));
router.param('postId', this.populatePostId.bind(this));
router.param('tagSlug', this.populateTagSlug.bind(this));
router.param('commentId', commentService.populateCommentId.bind(commentService));
router.post('/:postSlug/comment/:commentId/block-author', authRequired, upload.none(), this.postBlockCommentAuthor.bind(this));
@ -72,12 +73,14 @@ class PostController extends SiteController {
limiterService.createMiddleware(limiterService.config.post.getIndex),
this.getAuthorView.bind(this),
);
router.get('/authors',
limiterService.createMiddleware(limiterService.config.post.getAllAuthorsView),
this.getAllAuthorsView.bind(this),
limiterService.createMiddleware(limiterService.config.post.getAllAuthorsView),
this.getAllAuthorsView.bind(this),
);
router.get('/tags', this.getTagIndex.bind(this));
router.get('/:postSlug',
limiterService.createMiddleware(limiterService.config.post.getView),
this.getView.bind(this),
@ -101,7 +104,9 @@ class PostController extends SiteController {
this.deletePost.bind(this),
);
router.get('/*', this.badRoute.bind(this) );
router.get('/tag/:tagSlug', this.getTagSearchView.bind(this));
}
async populateUsername (req, res, next, username) {
@ -319,7 +324,7 @@ class PostController extends SiteController {
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to fetch more commnets', { postId: res.locals.post._id, error });
this.log.error('failed to fetch more comments', { postId: res.locals.post._id, error });
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -338,9 +343,9 @@ class PostController extends SiteController {
throw new SiteError(403, 'The post is not published');
}
}
await resourceService.recordView(req, 'Post', res.locals.post._id);
if (res.locals.post.status === 'published') {
await resourceService.recordView(req, 'Post', res.locals.post._id);
}
res.locals.countPerPage = 20;
res.locals.pagination = this.getPaginationParameters(req, res.locals.countPerPage);
@ -409,9 +414,9 @@ class PostController extends SiteController {
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
const {authors , totalAuthorCount }= await userService.getAuthors(res.locals.pagination);
res.locals.authors = authors;
res.locals.totalAuthorCount = totalAuthorCount;
const {authors , totalAuthorCount } = await userService.getAuthors(res.locals.pagination);
res.locals.authors = authors;
res.locals.totalAuthorCount = totalAuthorCount;
res.render('post/author/all');
} catch (error) {
return next(error);
@ -442,13 +447,61 @@ class PostController extends SiteController {
});
}
}
async populateTagSlug (req, res, next, tagSlug) {
const { post: postService } = this.dtp.services;
try {
var allPosts = false;
var statusArray = ['published'];
if (req.user) {
if (req.user.flags.isAdmin) {
statusArray.push('draft');
allPosts = true;
}
}
res.locals.allPosts = allPosts;
res.locals.tagSlug = tagSlug;
res.locals.tag = tagSlug.replace("_", " ");
res.locals.pagination = this.getPaginationParameters(req, 12);
const {posts, totalPosts} = await postService.getByTags(res.locals.tag, res.locals.pagination, statusArray);
res.locals.posts = posts;
res.locals.totalPosts = totalPosts;
return next();
} catch (error) {
this.log.error('failed to populate tagSlug', { tagSlug, error });
return next(error);
}
}
async getTagSearchView (req, res) {
try {
res.locals.pageTitle = `Tag ${res.locals.tag} on ${this.dtp.config.site.name}`;
res.render('post/tag/view');
} catch (error) {
this.log.error('failed to service post view', { postId: res.locals.post._id, error });
throw SiteError("Error getting tag view:", error );
}
}
async getTagIndex (req, res, next) {
const { post: postService } = this.dtp.services;
try {
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.posts = await postService.getPosts(res.locals.pagination);
res.render('post/tag/index');
} catch (error) {
return next(error);
}
}
}
module.exports = {
slug: 'post',
name: 'post',
create: async (dtp) => {
let controller = new PostController(dtp);
return controller;
},
className: 'PostController',
create: async (dtp) => { return new PostController(dtp); },
};

@ -18,12 +18,17 @@ class UserController extends SiteController {
async start ( ) {
const { dtp } = this;
const {
csrfToken: csrfTokenService,
limiter: limiterService,
otpAuth: otpAuthService,
session: sessionService,
} = dtp.services;
const upload = this.createMulter();
const upload = this.createMulter('user', {
limits: {
fileSize: 1024 * 1000 * 5, // 5MB
},
});
const router = express.Router();
dtp.app.use('/user', router);
@ -60,8 +65,10 @@ class UserController extends SiteController {
return next();
}
router.param('username', this.populateUsername.bind(this));
router.param('userId', this.populateUserId.bind(this));
router.param('localUsername', this.populateLocalUsername.bind(this));
router.param('coreUsername', this.populateCoreUsername.bind(this));
router.param('localUserId', this.populateLocalUserId.bind(this));
router.param('coreUserId', this.populateCoreUserId.bind(this));
router.post(
@ -73,7 +80,7 @@ class UserController extends SiteController {
);
router.post(
'/:userId/profile-photo',
'/:localUserId/profile-photo',
limiterService.createMiddleware(limiterService.config.user.postProfilePhoto),
checkProfileOwner,
upload.single('imageFile'),
@ -81,7 +88,7 @@ class UserController extends SiteController {
);
router.post(
'/:userId/settings',
'/:localUserId/settings',
limiterService.createMiddleware(limiterService.config.user.postUpdateSettings),
checkProfileOwner,
upload.none(),
@ -91,6 +98,7 @@ class UserController extends SiteController {
router.post(
'/',
limiterService.createMiddleware(limiterService.config.user.postCreate),
csrfTokenService.middleware({ name: 'user-create' }),
this.postCreateUser.bind(this),
);
@ -124,7 +132,7 @@ class UserController extends SiteController {
);
router.get(
'/:userId/settings',
'/:localUsername/settings',
limiterService.createMiddleware(limiterService.config.user.getSettings),
authRequired,
otpMiddleware,
@ -132,7 +140,7 @@ class UserController extends SiteController {
this.getUserSettingsView.bind(this),
);
router.get(
'/:username',
'/:localUsername',
limiterService.createMiddleware(limiterService.config.user.getUserProfile),
authRequired,
otpMiddleware,
@ -148,54 +156,127 @@ class UserController extends SiteController {
);
}
async populateUsername (req, res, next, username) {
const { user: userService } = this.dtp.services;
async populateCoreUsername (req, res, next, coreUsername) {
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
res.locals.userProfile = await userService.getPublicProfile('User', username);
if (!res.locals.userProfile) {
throw new SiteError(404, 'Member not found');
res.locals.username = userService.filterUsername(coreUsername);
res.locals.userProfileId = await userService.getCoreUserId(res.locals.username);
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) {
this.log.error('failed to populate username with public profile', { username, error });
this.log.error('failed to populate core username', { coreUsername, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateCoreUsername',
message: `failed to populate Core user: ${error.message}`,
data: { coreUsername, error },
});
return next(error);
}
}
async populateUserId (req, res, next, userId) {
const { user: userService } = this.dtp.services;
try {
userId = mongoose.Types.ObjectId(userId);
} catch (error) {
return next(new SiteError(406, 'Invalid User'));
}
async populateCoreUserId (req, res, next, coreUserId) {
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
res.locals.userProfile = await userService.getUserAccount(userId);
res.locals.userProfileId = mongoose.Types.ObjectId(coreUserId);
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);
}
if (!res.locals.userProfile) {
throw new SiteError(404, 'Core member not found');
}
return next();
} catch (error) {
this.log.error('failed to populate userId', { userId, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateCoreUserId',
message: `failed to populate core user: ${error.message}`,
data: { coreUserId, error },
});
return next(error);
}
}
async populateCoreUserId (req, res, next, coreUserId) {
const { coreNode: coreNodeService } = this.dtp.services;
async populateLocalUsername (req, res, next, username) {
const {
logan: loganService,
user: userService,
} = this.dtp.services;
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) {
return next(new SiteError(406, 'Invalid User'));
this.log.error('failed to populate local username', { username, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateLocalUsername',
message: `failed to populate local user: ${error.message}`,
data: { username, error },
});
return next(error);
}
}
async populateLocalUserId (req, res, next, userId) {
const {
logan: loganService,
user: userService,
} = this.dtp.services;
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();
} catch (error) {
this.log.error('failed to populate coreUserId', { coreUserId, error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'populateLocalUserId',
message: `failed to populate local user: ${error.message}`,
data: { userId, error },
});
return next(error);
}
}
async postCreateUser (req, res, next) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
// verify that the request has submitted a captcha
if ((typeof req.body.captcha !== 'string') || req.body.captcha.length === 0) {
@ -213,11 +294,42 @@ class UserController extends SiteController {
// create the user account
res.locals.user = await userService.create(req.body);
const form = Object.assign(req.body);
if (form.password) {
delete form.password;
}
if (form.passwordv) {
delete form.passwordv;
}
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postCreateUser',
data: {
form,
user: {
_id: res.locals.user._id,
username: res.locals.user.username,
},
},
});
// log the user in
req.login(res.locals.user, (error) => {
if (error) {
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postCreateUser',
message: `failed to start user session: ${error.message}`,
data: { error },
});
return next(error);
}
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postCreateUser',
message: 'user session started',
});
res.redirect(`/user/${res.locals.user.username}`);
});
} catch (error) {
@ -227,19 +339,35 @@ class UserController extends SiteController {
}
async postProfilePhoto (req, res) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
const displayList = this.createDisplayList('profile-photo');
await userService.updatePhoto(req.user, req.file);
const displayList = this.createDisplayList('profile-photo');
displayList.showNotification(
'Profile photo updated successfully.',
'success',
'bottom-center',
2000,
);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postProfilePhoto',
message: 'profile photo updated',
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to update profile photo', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postProfilePhoto',
message: `failed to update profile photo: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -248,19 +376,35 @@ class UserController extends SiteController {
}
async postHeaderImage (req, res) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
const displayList = this.createDisplayList('header-image');
await userService.updateHeaderImage(req.user, req.file);
const displayList = this.createDisplayList('header-image');
displayList.showNotification(
'Header image updated successfully.',
'success',
'bottom-center',
2000,
);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postHeaderImage',
message: 'header image updated',
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to update header image', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postHeaderImage',
message: `failed to update header image: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -269,16 +413,30 @@ class UserController extends SiteController {
}
async postUpdateCoreSettings (req, res) {
const { coreNode: coreNodeService } = this.dtp.services;
const {
coreNode: coreNodeService,
logan: loganService,
} = this.dtp.services;
try {
const displayList = this.createDisplayList('app-settings');
await coreNodeService.updateUserSettings(req.user, req.body);
const displayList = this.createDisplayList('app-settings');
displayList.reload();
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postUpdateCoreSettings',
message: 'CoreUser settings updated',
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to update CoreUser settings', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postUpdateCoreSettings',
message: `failed to update CoreUser settings: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -287,16 +445,30 @@ class UserController extends SiteController {
}
async postUpdateSettings (req, res) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
await userService.updateSettings(req.user, req.body);
const displayList = this.createDisplayList('app-settings');
displayList.reload();
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'postUpdateSettings',
message: 'account settings updates',
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to update account settings', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'postUpdateSettings',
message: `failed to update account settings: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -309,13 +481,26 @@ class UserController extends SiteController {
}
async getOtpDisable (req, res) {
const { otpAuth: otpAuthService } = this.dtp.services;
const {
logan: loganService,
otpAuth: otpAuthService,
} = this.dtp.services;
try {
await otpAuthService.destroyOtpSession(req, 'Account');
await otpAuthService.removeForUser(req.user, 'Account');
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getOtpDisable',
message: 'one-time passwords disabled',
});
res.render('user/otp-disabled');
} catch (error) {
this.log.error('failed to disable OTP service for Account', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getOtpDisable',
message: `failed to disable OTP: ${error.message}`,
data: { error },
});
res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -324,44 +509,71 @@ class UserController extends SiteController {
}
async getCoreUserSettingsView (req, res, next) {
const { otpAuth: otpAuthService } = this.dtp.services;
const {
logan: loganService,
otpAuth: otpAuthService,
} = this.dtp.services;
try {
res.locals.hasOtpAccount = await otpAuthService.isUserProtected(req.user, 'Account');
res.locals.startTab = req.query.st || 'watch';
res.render('user/settings-core');
} catch (error) {
this.log.error('failed to render CoreUser settings view', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getCoreUserSettingsView',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getUserSettingsView (req, res, next) {
const { otpAuth: otpAuthService } = this.dtp.services;
const {
logan: loganService,
otpAuth: otpAuthService,
} = this.dtp.services;
try {
res.locals.hasOtpAccount = await otpAuthService.isUserProtected(req.user, 'Account');
res.locals.startTab = req.query.st || 'watch';
res.render('user/settings');
} catch (error) {
this.log.error('failed to render user settings view', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getUserSettingsView',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async getUserView (req, res, next) {
const { comment: commentService } = this.dtp.services;
const {
comment: commentService,
logan: loganService,
} = this.dtp.services;
try {
res.locals.pageTitle = `${res.locals.userProfile.username}'s Profile`;
res.locals.pagination = this.getPaginationParameters(req, 20);
res.locals.commentHistory = await commentService.getForAuthor(req.user, res.locals.pagination);
res.render('user/profile');
} catch (error) {
this.log.error('failed to produce user profile view', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getUserView',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
async deleteProfilePhoto (req, res) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
const displayList = this.createDisplayList('app-settings');
await userService.removePhoto(req.user);
@ -371,9 +583,19 @@ class UserController extends SiteController {
'bottom-center',
2000,
);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'deleteProfilePhoto',
message: 'profile photo removed',
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to remove profile photo', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'deleteProfilePhoto',
message: `failed to remove profile photo: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -382,7 +604,10 @@ class UserController extends SiteController {
}
async deleteHeaderImage (req, res) {
const { user: userService } = this.dtp.services;
const {
logan: loganService,
user: userService,
} = this.dtp.services;
try {
const displayList = this.createDisplayList('remove-header-image');
await userService.removeHeaderImage(req.user);
@ -392,9 +617,19 @@ class UserController extends SiteController {
'bottom-center',
2000,
);
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'deleteHeaderImage',
message: 'profile header image removed',
});
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to remove header image', { error });
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'deleteHeaderImage',
message: `failed to remove header image: ${error.message}`,
data: { error },
});
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
@ -406,5 +641,6 @@ class UserController extends SiteController {
module.exports = {
slug: 'user',
name: 'user',
className: 'UserController',
create: async (dtp) => { return new UserController(dtp); },
};

@ -77,5 +77,6 @@ class VenueController extends SiteController {
module.exports = {
slug: 'venue',
name: 'venue',
className: 'VenueController',
create: async (dtp) => { return new VenueController(dtp); },
};

@ -65,23 +65,63 @@ class WelcomeController extends SiteController {
}
async getSignupView (req, res) {
const {
csrfToken: csrfTokenService,
logan: loganService,
} = this.dtp.services;
req.csrfToken = await csrfTokenService.create(req, {
name: 'user-create',
expiresMinutes: 20,
});
req.session.captcha = req.session.captcha || { };
req.session.captcha.signup = captcha.randomText(4 + Math.floor(Math.random()*4));
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getSignupView',
message: 'serving new member signup view',
});
res.render('welcome/signup');
}
async getLoginView (req, res) {
const { logan: loganService } = this.dtp.services;
res.locals.loginResult = req.session.loginResult;
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getLoginView',
message: 'serving member login view',
});
res.render('welcome/login');
}
async getHomeView (req, res) {
res.render('welcome/index');
async getHomeView (req, res, next) {
const { logan: loganService } = this.dtp.services;
try {
loganService.sendRequestEvent(module.exports, req, {
level: 'info',
event: 'getHomeView',
message: 'serving the Welcome home page',
});
res.render('welcome/index');
} catch (error) {
loganService.sendRequestEvent(module.exports, req, {
level: 'error',
event: 'getHomeView',
message: `failed to render the view: ${error.message}`,
data: { error },
});
return next(error);
}
}
}
module.exports = {
slug: 'welcome',
name: 'welcome',
className: 'WelcomeController',
create: async (dtp) => { return new WelcomeController(dtp); },
};
};

@ -29,4 +29,6 @@ const AnnouncementSchema = new Schema({
resourceStats: { type: ResourceStats, default: ResourceStatsDefaults, required: true },
});
module.exports = mongoose.model('Announcement', AnnouncementSchema);
module.exports = (conn) => {
return conn.model('Announcement', AnnouncementSchema);
};

@ -61,4 +61,6 @@ AttachmentSchema.index({
name: 'attachment_item_idx',
});
module.exports = mongoose.model('Attachment', AttachmentSchema);
module.exports = (conn) => {
return conn.model('Attachment', AttachmentSchema);
};

@ -22,4 +22,6 @@ const CategorySchema = new Schema({
},
});
module.exports = mongoose.model('Category', CategorySchema);
module.exports = (conn) => {
return conn.model('Category', CategorySchema);
};

@ -28,4 +28,6 @@ const ChatMessageSchema = new Schema({
attachments: { type: [Schema.ObjectId], ref: 'Attachment' },
});
module.exports = mongoose.model('ChatMessage', ChatMessageSchema);
module.exports = (conn) => {
return conn.model('ChatMessage', ChatMessageSchema);
};

@ -27,4 +27,6 @@ ChatRoomInviteSchema.index({
name: 'chatroom_invite_unique_idx',
});
module.exports = mongoose.model('ChatRoomInvite', ChatRoomInviteSchema);
module.exports = (conn) => {
return conn.model('ChatRoomInvite', ChatRoomInviteSchema);
};

@ -41,4 +41,6 @@ ChatRoomSchema.index({
name: 'chatroom_public_open_idx',
});
module.exports = mongoose.model('ChatRoom', ChatRoomSchema);
module.exports = (conn) => {
return conn.model('ChatRoom', ChatRoomSchema);
};

@ -72,4 +72,6 @@ CommentSchema.index({
name: 'comment_replies',
});
module.exports = mongoose.model('Comment', CommentSchema);
module.exports = (conn) => {
return conn.model('Comment', CommentSchema);
};

@ -16,4 +16,6 @@ const ConnectTokenSchema = new Schema({
claimed: { type: Date },
});
module.exports = mongoose.model('ConnectToken', ConnectTokenSchema);
module.exports = (conn) => {
return conn.model('ConnectToken', ConnectTokenSchema);
};

@ -28,4 +28,6 @@ ContentReportSchema.index({
name: 'unique_user_content_report',
});
module.exports = mongoose.model('ContentReport', ContentReportSchema);
module.exports = (conn) => {
return conn.model('ContentReport', ContentReportSchema);
};

@ -23,4 +23,6 @@ ContentVoteSchema.index({
name: 'unique_user_content_vote',
});
module.exports = mongoose.model('ContentVote', ContentVoteSchema);
module.exports = (conn) => {
return conn.model('ContentVote', ContentVoteSchema);
};

@ -30,4 +30,6 @@ const CoreNodeConnectSchema = new Schema({
},
});
module.exports = mongoose.model('CoreNodeConnect', CoreNodeConnectSchema);
module.exports = (conn) => {
return conn.model('CoreNodeConnect', CoreNodeConnectSchema);
};

@ -35,4 +35,6 @@ const CoreNodeRequestSchema = new Schema({
},
});
module.exports = mongoose.model('CoreNodeRequest', CoreNodeRequestSchema);
module.exports = (conn) => {
return conn.model('CoreNodeRequest', CoreNodeRequestSchema);
};

@ -45,4 +45,6 @@ CoreNodeSchema.index({
name: 'core_address_idx',
});
module.exports = mongoose.model('CoreNode', CoreNodeSchema);
module.exports = (conn) => {
return conn.model('CoreNode', CoreNodeSchema);
};

@ -52,4 +52,6 @@ CoreUserSchema.index({
name: 'core_username_lc_unique',
});
module.exports = mongoose.model('CoreUser', CoreUserSchema);
module.exports = (conn) => {
return conn.model('CoreUser', CoreUserSchema);
};

@ -17,4 +17,6 @@ const CsrfTokenSchema = new Schema({
ip: { type: String, required: true },
});
module.exports = mongoose.model('CsrfToken', CsrfTokenSchema);
module.exports = (conn) => {
return conn.model('CsrfToken', CsrfTokenSchema);
};

@ -31,4 +31,6 @@ EmailBlacklistSchema.index({
},
});
module.exports = mongoose.model('EmailBlacklist', EmailBlacklistSchema);
module.exports = (conn) => {
return conn.model('EmailBlacklist', EmailBlacklistSchema);
};

@ -12,4 +12,6 @@ const EmailBodySchema = new Schema({
body: { type: String, required: true },
});
module.exports = mongoose.model('EmailBody', EmailBodySchema);
module.exports = (conn) => {
return conn.model('EmailBody', EmailBodySchema);
};

@ -16,4 +16,6 @@ const EmailLogSchema = new Schema({
messageId: { type: String },
});
module.exports = mongoose.model('EmailLog', EmailLogSchema);
module.exports = (conn) => {
return conn.model('EmailLog', EmailLogSchema);
};

@ -15,4 +15,6 @@ const EmailVerifySchema = new Schema({
token: { type: String, required: true },
});
module.exports = mongoose.model('EmailVerify', EmailVerifySchema);
module.exports = (conn) => {
return conn.model('EmailVerify', EmailVerifySchema);
};

@ -17,4 +17,6 @@ const EmailSchema = new Schema({
content: { type: Schema.ObjectId, required: true, index: true, refPath: 'contentType' },
});
module.exports = mongoose.model('Email', EmailSchema);
module.exports = (conn) => {
return conn.model('Email', EmailSchema);
};

@ -36,4 +36,6 @@ const EmojiReactionSchema = new Schema({
timestamp: { type: Number },
});
module.exports = mongoose.model('EmojiReaction', EmojiReactionSchema);
module.exports = (conn) => {
return conn.model('EmojiReaction', EmojiReactionSchema);
};

@ -23,4 +23,6 @@ FeedEntrySchema.index({
name: 'feed_entry_by_feed_idx',
});
module.exports = mongoose.model('FeedEntry', FeedEntrySchema);
module.exports = (conn) => {
return conn.model('FeedEntry', FeedEntrySchema);
};

@ -18,4 +18,6 @@ const FeedSchema = new Schema({
published: { type: Date },
});
module.exports = mongoose.model('Feed', FeedSchema);
module.exports = (conn) => {
return conn.model('Feed', FeedSchema);
};

@ -32,4 +32,6 @@ const ImageSchema = new Schema({
},
});
module.exports = mongoose.model('Image', ImageSchema);
module.exports = (conn) => {
return conn.model('Image', ImageSchema);
};

@ -57,4 +57,6 @@ KaleidoscopeEventSchema.index({
name: 'evtsrc_site_author_index',
});
module.exports = mongoose.model('KaleidoscopeEvent', KaleidoscopeEventSchema);
module.exports = (conn) => {
return conn.model('KaleidoscopeEvent', KaleidoscopeEventSchema);
};

@ -29,4 +29,6 @@ const LogSchema = new Schema({
metadata: { type: Schema.Types.Mixed },
});
module.exports = mongoose.model('Log', LogSchema);
module.exports = (conn) => {
return conn.model('Log', LogSchema);
};

@ -0,0 +1,55 @@
// media-router.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const STATUS_LIST = [
'starting', // the router process is starting and configuring itself
'active', // the router is active and available for service
'capacity', // the router is at or over capacity
'closing', // the router is closing/shutting down
'closed', // the router no longer exists
];
const RouterHostSchema = new Schema({
address: { type: String, required: true, index: 1 },
port: { type: Number, required: true },
});
/*
* A Router is a "multi-user conference call instance" somewhere on the
* infrastructure. This model helps us manage them, balance load across them,
* and route calls to and between them (for scale).
*
* These records are created when a call is being created, and are commonly
* left in the database after all call participants have left. An expires index
* is used to sweep up router records after 30 days. This allows us to perform
* statistics aggregation on router use and store aggregated results as part of
* long-term reporting.
*/
const MediaRouterSchema = new Schema({
created: { type: Date, default: Date.now, required: true, expires: '30d' },
lastActivity: { type: Date, default: Date.now, required: true },
status: { type: String, enum: STATUS_LIST, default: 'starting', required: true, index: true },
name: { type: String },
description: { type: String },
access: {
isPrivate: { type: Boolean, default: true, required: true },
passcodeHash: { type: String, select: false },
},
host: { type: RouterHostSchema, required: true, select: false },
stats: {
routerCount: { type: Number, default: 0, required: true },
consumerCount: { type: Number, default: 0, required: true },
producerCount: { type: Number, default: 0, required: true },
}
});
module.exports = (conn) => {
return conn.model('MediaRouter', MediaRouterSchema);
};

@ -0,0 +1,45 @@
// media-worker.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const STATUS_LIST = [
'starting', // the router process is starting and configuring itself
'active', // the router is active and available for service
'capacity', // the router is at or over capacity
'closing', // the router is closing/shutting down
'closed', // the router no longer exists
];
const WebRtcListenSchema = new Schema({
protocol: { type: String, enum: ['tcp','udp'], required: true },
ip: { type: String, required: true },
port: { type: Number, required: true },
});
/*
* A media worker is a host process with one or more MediaRouter instances
* processing multi-user conference calls.
*/
const MediaWorkerSchema = new Schema({
created: { type: Date, default: Date.now, required: true, expires: '30d' },
lastActivity: { type: Date, default: Date.now, required: true },
status: { type: String, enum: STATUS_LIST, default: 'starting', required: true, index: true },
webRtcServer: {
listenInfos: { type: [WebRtcListenSchema] },
},
stats: {
routerCount: { type: Number, default: 0, required: true },
consumerCount: { type: Number, default: 0, required: true },
producerCount: { type: Number, default: 0, required: true },
}
});
module.exports = (conn) => {
return conn.model('MediaWorker', MediaWorkerSchema);
};

@ -72,4 +72,6 @@ const NetHostStatsSchema = new Schema({
network: { type: [NetworkInterfaceStatsSchema], required: true },
});
module.exports = mongoose.model('NetHostStats', NetHostStatsSchema);
module.exports = (conn) => {
return conn.model('NetHostStats', NetHostStatsSchema);
};

@ -42,4 +42,6 @@ const NetHostSchema = new Schema({
network: { type: [NetworkInterfaceSchema] },
});
module.exports = mongoose.model('NetHost', NetHostSchema);
module.exports = (conn) => {
return conn.model('NetHost', NetHostSchema);
};

@ -18,4 +18,6 @@ const NewsletterRecipientSchema = new Schema({
},
});
module.exports = mongoose.model('NewsletterRecipient', NewsletterRecipientSchema);
module.exports = (conn) => {
return conn.model('NewsletterRecipient', NewsletterRecipientSchema);
};

@ -28,4 +28,6 @@ const NewsletterSchema = new Schema({
},
});
module.exports = mongoose.model('Newsletter', NewsletterSchema);
module.exports = (conn) => {
return conn.model('Newsletter', NewsletterSchema);
};

@ -16,4 +16,6 @@ const OAuth2AuthorizationCodeSchema = new Schema({
scopes: { type: [String], required: true },
});
module.exports = mongoose.model('OAuth2AuthorizationCode', OAuth2AuthorizationCodeSchema);
module.exports = (conn) => {
return conn.model('OAuth2AuthorizationCode', OAuth2AuthorizationCodeSchema);
};

@ -40,4 +40,6 @@ OAuth2ClientSchema.index({
unique: true,
});
module.exports = mongoose.model('OAuth2Client', OAuth2ClientSchema);
module.exports = (conn) => {
return conn.model('OAuth2Client', OAuth2ClientSchema);
};

@ -24,4 +24,6 @@ OAuth2TokenSchema.index({
name: 'oauth2_token_unique',
});
module.exports = mongoose.model('OAuth2Token', OAuth2TokenSchema);
module.exports = (conn) => {
return conn.model('OAuth2Token', OAuth2TokenSchema);
};

@ -33,4 +33,6 @@ OtpAccountSchema.index({
name: 'otp_user_svc_uniq_idx',
});
module.exports = mongoose.model('OtpAccount', OtpAccountSchema);
module.exports = (conn) => {
return conn.model('OtpAccount', OtpAccountSchema);
};

@ -26,4 +26,6 @@ const PageSchema = new Schema({
},
});
module.exports = mongoose.model('Page', PageSchema);
module.exports = (conn) => {
return conn.model('Page', PageSchema);
};

@ -43,4 +43,6 @@ PostSchema.index({
name: 'post_author_by_type',
});
module.exports = mongoose.model('Post', PostSchema);
module.exports = (conn) => {
return conn.model('Post', PostSchema);
};

@ -29,4 +29,6 @@ ResourceViewSchema.index({
name: 'res_view_daily_unique',
});
module.exports = mongoose.model('ResourceView', ResourceViewSchema);
module.exports = (conn) => {
return conn.model('ResourceView', ResourceViewSchema);
};

@ -26,4 +26,6 @@ ResourceVisitSchema.index({
name: 'resource_visits_for_user',
});
module.exports = mongoose.model('ResourceVisit', ResourceVisitSchema);
module.exports = (conn) => {
return conn.model('ResourceVisit', ResourceVisitSchema);
};

@ -16,4 +16,6 @@ const SiteLinkSchema = new Schema({
target: { type: String },
});
module.exports = mongoose.model('SiteLink', SiteLinkSchema);
module.exports = (conn) => {
return conn.model('SiteLink', SiteLinkSchema);
};

@ -43,4 +43,6 @@ const StickerSchema = new Schema({
encoded: { type: StickerMediaSchema },
});
module.exports = mongoose.model('Sticker', StickerSchema);
module.exports = (conn) => {
return conn.model('Sticker', StickerSchema);
};

@ -12,4 +12,6 @@ const UserBlockSchema = new Schema({
blockedUsers: { type: [Schema.ObjectId], ref: 'User' },
});
module.exports = mongoose.model('UserBlock', UserBlockSchema);
module.exports = (conn) => {
return conn.model('UserBlock', UserBlockSchema);
};

@ -24,4 +24,6 @@ const UserNotificationSchema = new Schema({
event: { type: Schema.ObjectId, required: true, ref: 'KaleidoscopeEvent' },
});
module.exports = mongoose.model('UserNotification', UserNotificationSchema);
module.exports = (conn) => {
return conn.model('UserNotification', UserNotificationSchema);
};

@ -24,4 +24,6 @@ const UserSubscriptionSchema = new Schema({
subscriptions: { type: [SubscriptionSchema] },
});
module.exports = mongoose.model('UserSubscription', UserSubscriptionSchema);
module.exports = (conn) => {
return conn.model('UserSubscription', UserSubscriptionSchema);
};

@ -22,17 +22,18 @@ const {
const UserSchema = new Schema({
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_lc: { type: String, required: true, lowercase: true, unique: true, index: 1 },
passwordSalt: { type: String, required: true },
password: { type: String, required: true },
passwordSalt: { type: String, required: true, select: false },
password: { type: String, required: true, select: false },
displayName: { type: String },
bio: { type: String, maxlength: 300 },
picture: {
large: { type: Schema.ObjectId, ref: 'Image' },
small: { type: Schema.ObjectId, ref: 'Image' },
},
header: { type: Schema.ObjectId, ref: 'Image' },
badges: { type: [String] },
flags: { type: UserFlagsSchema, select: false },
permissions: { type: UserPermissionsSchema, select: false },
@ -58,4 +59,6 @@ UserSchema.virtual('hasAuthorDashboard').get( function ( ) {
this.flags.isAdmin;
});
module.exports = mongoose.model('User', UserSchema);
module.exports = (conn) => {
return conn.model('User', UserSchema);
};

@ -43,4 +43,6 @@ const VenueChannelStatusSchema = new Schema({
},
});
module.exports = mongoose.model('VenueChannelStatus', VenueChannelStatusSchema);
module.exports = (conn) => {
return conn.model('VenueChannelStatus', VenueChannelStatusSchema);
};

@ -22,4 +22,6 @@ const VenueChannelSchema = new Schema({
credentials: { type: ChannelCredentialsSchema, required: true, select: false },
});
module.exports = mongoose.model('VenueChannel', VenueChannelSchema);
module.exports = (conn) => {
return conn.model('VenueChannel', VenueChannelSchema);
};

@ -118,5 +118,6 @@ class AnnouncementService extends SiteService {
module.exports = {
slug: 'announcement',
name: 'announcement',
className: 'AnnouncementService',
create: (dtp) => { return new AnnouncementService(dtp); },
};

@ -182,5 +182,6 @@ class AttachmentService extends SiteService {
module.exports = {
slug: 'attachment',
name: 'attachment',
className: 'AttachmentService',
create: (dtp) => { return new AttachmentService(dtp); },
};

@ -59,5 +59,6 @@ class CacheService extends SiteService {
module.exports = {
slug: 'cache',
name: 'cache',
className: 'CacheService',
create: (dtp) => { return new CacheService(dtp); },
};

@ -473,7 +473,12 @@ class ChatService extends SiteService {
async createMessage (author, messageDefinition) {
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) {
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 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) {
throw new SiteError(403, 'You are not permitted to chat');
}
@ -773,10 +784,76 @@ class ChatService extends SiteService {
return reaction.toObject();
}
async removeForUser (user) {
const { logan: loganService } = this.dtp.services;
let roomCount = 0, messageCount = 0;
await ChatRoom
.find({ owner: user._id })
.populate(this.populateChatRoom)
.cursor()
.eachAsync(async (room) => {
try {
await this.deleteRoom(room);
++roomCount;
} catch (error) {
loganService.sendEvent(module.exports, {
level: 'error',
event: 'removeForUser',
message: `failed to remove chat room: ${error.message}`,
data: {
room: {
_id: room._id,
name: room.name,
},
error,
},
});
// fall through
}
});
await ChatMessage
.find({ author: user._id })
.cursor()
.eachAsync(async (message) => {
try {
await this.removeMessage(message);
++messageCount;
} catch (error) {
loganService.sendEvent(module.exports, {
level: 'error',
event: 'removeForUser',
message: `failed to remove chat message: ${error.message}`,
data: {
message: {
_id: message._id,
},
error,
},
});
// fall through
}
});
loganService.sendEvent(module.exports, {
level: 'info',
event: 'removeForUser',
data: {
user: {
_id: user._id,
username: user.username,
},
roomCount, messageCount,
},
});
}
}
module.exports = {
slug: 'chat',
name: 'chat',
className: 'ChatService',
create: (dtp) => { return new ChatService(dtp); },
};

@ -320,10 +320,23 @@ class CommentService extends SiteService {
await Comment.deleteOne({ _id: comment._id });
}, 4);
}
async removeForAuthor (author) {
await Comment
.find({ // index: 'comment_author_by_type'
authorType: author.type,
author: author._id,
})
.cursor()
.eachAsync(async (comment) => {
await this.remove(comment);
});
}
}
module.exports = {
slug: 'comment',
name: 'comment',
className: 'CommentService',
create: (dtp) => { return new CommentService(dtp); },
};

@ -117,6 +117,10 @@ class ContentReportService extends SiteService {
await ContentReport.deleteMany({ resource: resource._id });
}
async removeForUser (user) {
await ContentReport.deleteMany({ user: user._id });
}
async removeReport (report) {
await ContentReport.deleteOne({ _id: report._id });
}
@ -125,5 +129,6 @@ class ContentReportService extends SiteService {
module.exports = {
slug: 'content-report',
name: 'contentReport',
className: 'ContentReportService',
create: (dtp) => { return new ContentReportService(dtp); },
};

@ -95,10 +95,30 @@ class ContentVoteService extends SiteService {
this.log.info('removing all votes for resource', { resourceId: resource._id });
await ContentVote.deleteMany({ resource: resource._id });
}
async removeForUser (user) {
await ContentVote
.find({ user: user._id })
.cursor()
.eachAsync(async (vote) => {
const updateOp = { $inc: { } };
if (vote.vote === 'up') {
updateOp.$inc['resourceStats.upvoteCount'] = -1;
} else {
updateOp.$inc['resourceStats.downvoteCount'] = -1;
}
const resourceModel = mongoose.model(vote.resourceType);
await resourceModel.updateOne({ _id: vote.resource }, updateOp);
});
await ContentVote.deleteMany({ user: user._id });
}
}
module.exports = {
slug: 'content-vote',
name: 'contentVote',
className: 'ContentVoteService',
create: (dtp) => { return new ContentVoteService(dtp); },
};

@ -718,5 +718,6 @@ class CoreNodeService extends SiteService {
module.exports = {
slug: 'core-node',
name: 'coreNode',
className: 'CoreNodeService',
create: (dtp) => { return new CoreNodeService(dtp); },
};

@ -60,5 +60,6 @@ class CryptoService extends SiteService {
module.exports = {
slug: 'crypto',
name: 'crypto',
className: 'CryptoService',
create: (dtp) => { return new CryptoService(dtp); },
};

@ -69,10 +69,15 @@ class CsrfTokenService extends SiteService {
csrfToken.name = `csrf-token-${options.name}`;
return csrfToken;
}
async removeForUser (user) {
await CsrfToken.deleteMany({ user: user._id });
}
}
module.exports = {
slug: 'csrf-token',
name: 'csrfToken',
className: 'CsrfTokenService',
create: (dtp) => { return new CsrfTokenService(dtp); },
};

@ -272,5 +272,6 @@ class DashboardService extends SiteService {
module.exports = {
slug: 'dashboard',
name: 'dashboard',
className: 'DashboardService',
create: (dtp) => { return new DashboardService(dtp); },
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save