diff --git a/.env.default b/.env.default index e040a34..2516829 100644 --- a/.env.default +++ b/.env.default @@ -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 diff --git a/README.md b/README.md index bea1014..8dff739 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/app/controllers/admin/user.js b/app/controllers/admin/user.js index 214431c..fc1fd9d 100644 --- a/app/controllers/admin/user.js +++ b/app/controllers/admin/user.js @@ -22,37 +22,36 @@ 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); return next(); } catch (error) { return next(error); } } - async postUpdateUser (req, res, next) { + async postUpdateLocalUser (req, res, next) { const { user: userService } = this.dtp.services; try { - await userService.updateForAdmin(res.locals.userAccount, req.body); + await userService.updateLocalForAdmin(res.locals.userAccount, req.body); 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 +67,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) { diff --git a/app/controllers/hive/user.js b/app/controllers/hive/user.js index 7bee4e9..fa19802 100644 --- a/app/controllers/hive/user.js +++ b/app/controllers/hive/user.js @@ -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) => { diff --git a/app/controllers/user.js b/app/controllers/user.js index 909f16b..01371f6 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -23,7 +23,11 @@ class UserController extends SiteController { 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 +64,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 +79,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 +87,7 @@ class UserController extends SiteController { ); router.post( - '/:userId/settings', + '/:localUserId/settings', limiterService.createMiddleware(limiterService.config.user.postUpdateSettings), checkProfileOwner, upload.none(), @@ -124,7 +130,7 @@ class UserController extends SiteController { ); router.get( - '/:userId/settings', + '/:localUsername/settings', limiterService.createMiddleware(limiterService.config.user.getSettings), authRequired, otpMiddleware, @@ -132,7 +138,7 @@ class UserController extends SiteController { this.getUserSettingsView.bind(this), ); router.get( - '/:username', + '/:localUsername', limiterService.createMiddleware(limiterService.config.user.getUserProfile), authRequired, otpMiddleware, @@ -148,48 +154,84 @@ class UserController extends SiteController { ); } - async populateUsername (req, res, next, username) { + async populateCoreUsername (req, res, next, coreUsername) { const { 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 }); return next(error); } } - async populateUserId (req, res, next, userId) { + async populateCoreUserId (req, res, next, coreUserId) { const { user: userService } = this.dtp.services; try { - userId = mongoose.Types.ObjectId(userId); - } catch (error) { - return next(new SiteError(406, 'Invalid User')); - } - 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 }); + this.log.error('failed to populate core user id', { coreUserId, error }); return next(error); } } - async populateCoreUserId (req, res, next, coreUserId) { - const { coreNode: coreNodeService } = this.dtp.services; + async populateLocalUsername (req, res, next, username) { + const { 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 }); + return next(error); } + } + + async populateLocalUserId (req, res, next, userId) { + const { 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 }); + this.log.error('failed to populate local user id', { userId, error }); return next(error); } } @@ -229,8 +271,9 @@ class UserController extends SiteController { async postProfilePhoto (req, res) { const { 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', @@ -250,8 +293,9 @@ class UserController extends SiteController { async postHeaderImage (req, res) { const { 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', @@ -271,10 +315,9 @@ class UserController extends SiteController { async postUpdateCoreSettings (req, res) { const { coreNode: coreNodeService } = 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(); res.status(200).json({ success: true, displayList }); } catch (error) { diff --git a/app/models/announcement.js b/app/models/announcement.js index d7d8b51..7438408 100644 --- a/app/models/announcement.js +++ b/app/models/announcement.js @@ -29,4 +29,6 @@ const AnnouncementSchema = new Schema({ resourceStats: { type: ResourceStats, default: ResourceStatsDefaults, required: true }, }); -module.exports = mongoose.model('Announcement', AnnouncementSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Announcement', AnnouncementSchema); +}; \ No newline at end of file diff --git a/app/models/attachment.js b/app/models/attachment.js index 71c3b89..5ac8e3d 100644 --- a/app/models/attachment.js +++ b/app/models/attachment.js @@ -61,4 +61,6 @@ AttachmentSchema.index({ name: 'attachment_item_idx', }); -module.exports = mongoose.model('Attachment', AttachmentSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Attachment', AttachmentSchema); +}; \ No newline at end of file diff --git a/app/models/chat-message.js b/app/models/chat-message.js index ba963ca..7ccf67e 100644 --- a/app/models/chat-message.js +++ b/app/models/chat-message.js @@ -28,4 +28,6 @@ const ChatMessageSchema = new Schema({ attachments: { type: [Schema.ObjectId], ref: 'Attachment' }, }); -module.exports = mongoose.model('ChatMessage', ChatMessageSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ChatMessage', ChatMessageSchema); +}; \ No newline at end of file diff --git a/app/models/chat-room-invite.js b/app/models/chat-room-invite.js index f1d8fe6..dbe6eaf 100644 --- a/app/models/chat-room-invite.js +++ b/app/models/chat-room-invite.js @@ -27,4 +27,6 @@ ChatRoomInviteSchema.index({ name: 'chatroom_invite_unique_idx', }); -module.exports = mongoose.model('ChatRoomInvite', ChatRoomInviteSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ChatRoomInvite', ChatRoomInviteSchema); +}; \ No newline at end of file diff --git a/app/models/chat-room.js b/app/models/chat-room.js index 6d3e2ff..502647d 100644 --- a/app/models/chat-room.js +++ b/app/models/chat-room.js @@ -41,4 +41,6 @@ ChatRoomSchema.index({ name: 'chatroom_public_open_idx', }); -module.exports = mongoose.model('ChatRoom', ChatRoomSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ChatRoom', ChatRoomSchema); +}; \ No newline at end of file diff --git a/app/models/comment.js b/app/models/comment.js index 2aaa3de..8bd6558 100644 --- a/app/models/comment.js +++ b/app/models/comment.js @@ -72,4 +72,6 @@ CommentSchema.index({ name: 'comment_replies', }); -module.exports = mongoose.model('Comment', CommentSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Comment', CommentSchema); +}; \ No newline at end of file diff --git a/app/models/connect-token.js b/app/models/connect-token.js index 7f22340..c9ef76f 100644 --- a/app/models/connect-token.js +++ b/app/models/connect-token.js @@ -16,4 +16,6 @@ const ConnectTokenSchema = new Schema({ claimed: { type: Date }, }); -module.exports = mongoose.model('ConnectToken', ConnectTokenSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ConnectToken', ConnectTokenSchema); +}; \ No newline at end of file diff --git a/app/models/content-report.js b/app/models/content-report.js index 3e47c9d..e169e48 100644 --- a/app/models/content-report.js +++ b/app/models/content-report.js @@ -28,4 +28,6 @@ ContentReportSchema.index({ name: 'unique_user_content_report', }); -module.exports = mongoose.model('ContentReport', ContentReportSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ContentReport', ContentReportSchema); +}; \ No newline at end of file diff --git a/app/models/content-vote.js b/app/models/content-vote.js index 490b098..28b21df 100644 --- a/app/models/content-vote.js +++ b/app/models/content-vote.js @@ -23,4 +23,6 @@ ContentVoteSchema.index({ name: 'unique_user_content_vote', }); -module.exports = mongoose.model('ContentVote', ContentVoteSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ContentVote', ContentVoteSchema); +}; \ No newline at end of file diff --git a/app/models/core-node-connect.js b/app/models/core-node-connect.js index 599503e..355ca5b 100644 --- a/app/models/core-node-connect.js +++ b/app/models/core-node-connect.js @@ -30,4 +30,6 @@ const CoreNodeConnectSchema = new Schema({ }, }); -module.exports = mongoose.model('CoreNodeConnect', CoreNodeConnectSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreNodeConnect', CoreNodeConnectSchema); +}; \ No newline at end of file diff --git a/app/models/core-node-request.js b/app/models/core-node-request.js index df526f4..730d117 100644 --- a/app/models/core-node-request.js +++ b/app/models/core-node-request.js @@ -35,4 +35,6 @@ const CoreNodeRequestSchema = new Schema({ }, }); -module.exports = mongoose.model('CoreNodeRequest', CoreNodeRequestSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreNodeRequest', CoreNodeRequestSchema); +}; \ No newline at end of file diff --git a/app/models/core-node.js b/app/models/core-node.js index 81eda79..3a037af 100644 --- a/app/models/core-node.js +++ b/app/models/core-node.js @@ -45,4 +45,6 @@ CoreNodeSchema.index({ name: 'core_address_idx', }); -module.exports = mongoose.model('CoreNode', CoreNodeSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreNode', CoreNodeSchema); +}; \ No newline at end of file diff --git a/app/models/core-user.js b/app/models/core-user.js index 04e9e46..f91a347 100644 --- a/app/models/core-user.js +++ b/app/models/core-user.js @@ -52,4 +52,6 @@ CoreUserSchema.index({ name: 'core_username_lc_unique', }); -module.exports = mongoose.model('CoreUser', CoreUserSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CoreUser', CoreUserSchema); +}; \ No newline at end of file diff --git a/app/models/csrf-token.js b/app/models/csrf-token.js index 81d75df..2337094 100644 --- a/app/models/csrf-token.js +++ b/app/models/csrf-token.js @@ -17,4 +17,6 @@ const CsrfTokenSchema = new Schema({ ip: { type: String, required: true }, }); -module.exports = mongoose.model('CsrfToken', CsrfTokenSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('CsrfToken', CsrfTokenSchema); +}; \ No newline at end of file diff --git a/app/models/email-blacklist.js b/app/models/email-blacklist.js index ff03a3a..50281c6 100644 --- a/app/models/email-blacklist.js +++ b/app/models/email-blacklist.js @@ -31,4 +31,6 @@ EmailBlacklistSchema.index({ }, }); -module.exports = mongoose.model('EmailBlacklist', EmailBlacklistSchema); +module.exports = (conn) => { + return conn.model('EmailBlacklist', EmailBlacklistSchema); +}; \ No newline at end of file diff --git a/app/models/email-body.js b/app/models/email-body.js index e8e3824..c0ceb57 100644 --- a/app/models/email-body.js +++ b/app/models/email-body.js @@ -12,4 +12,6 @@ const EmailBodySchema = new Schema({ body: { type: String, required: true }, }); -module.exports = mongoose.model('EmailBody', EmailBodySchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmailBody', EmailBodySchema); +}; \ No newline at end of file diff --git a/app/models/email-log.js b/app/models/email-log.js index b15fdec..d196d85 100644 --- a/app/models/email-log.js +++ b/app/models/email-log.js @@ -16,4 +16,6 @@ const EmailLogSchema = new Schema({ messageId: { type: String }, }); -module.exports = mongoose.model('EmailLog', EmailLogSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmailLog', EmailLogSchema); +}; \ No newline at end of file diff --git a/app/models/email-verify.js b/app/models/email-verify.js index 9b86900..e1ebd49 100644 --- a/app/models/email-verify.js +++ b/app/models/email-verify.js @@ -15,4 +15,6 @@ const EmailVerifySchema = new Schema({ token: { type: String, required: true }, }); -module.exports = mongoose.model('EmailVerify', EmailVerifySchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmailVerify', EmailVerifySchema); +}; \ No newline at end of file diff --git a/app/models/email.js b/app/models/email.js index f76bcae..b745c5b 100644 --- a/app/models/email.js +++ b/app/models/email.js @@ -17,4 +17,6 @@ const EmailSchema = new Schema({ content: { type: Schema.ObjectId, required: true, index: true, refPath: 'contentType' }, }); -module.exports = mongoose.model('Email', EmailSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Email', EmailSchema); +}; \ No newline at end of file diff --git a/app/models/emoji-reaction.js b/app/models/emoji-reaction.js index 6ce84a2..ce87469 100644 --- a/app/models/emoji-reaction.js +++ b/app/models/emoji-reaction.js @@ -36,4 +36,6 @@ const EmojiReactionSchema = new Schema({ timestamp: { type: Number }, }); -module.exports = mongoose.model('EmojiReaction', EmojiReactionSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('EmojiReaction', EmojiReactionSchema); +}; \ No newline at end of file diff --git a/app/models/feed-entry.js b/app/models/feed-entry.js index 676888a..fa708f8 100644 --- a/app/models/feed-entry.js +++ b/app/models/feed-entry.js @@ -23,4 +23,6 @@ FeedEntrySchema.index({ name: 'feed_entry_by_feed_idx', }); -module.exports = mongoose.model('FeedEntry', FeedEntrySchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('FeedEntry', FeedEntrySchema); +}; \ No newline at end of file diff --git a/app/models/feed.js b/app/models/feed.js index e35f1d6..9ed21cd 100644 --- a/app/models/feed.js +++ b/app/models/feed.js @@ -19,4 +19,6 @@ const FeedSchema = new Schema({ published: { type: Date }, }); -module.exports = mongoose.model('Feed', FeedSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Feed', FeedSchema); +}; \ No newline at end of file diff --git a/app/models/image.js b/app/models/image.js index d31e6da..c2511c1 100644 --- a/app/models/image.js +++ b/app/models/image.js @@ -32,4 +32,6 @@ const ImageSchema = new Schema({ }, }); -module.exports = mongoose.model('Image', ImageSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Image', ImageSchema); +}; \ No newline at end of file diff --git a/app/models/kaleidoscope-event.js b/app/models/kaleidoscope-event.js index b76f33b..949547f 100644 --- a/app/models/kaleidoscope-event.js +++ b/app/models/kaleidoscope-event.js @@ -58,4 +58,6 @@ KaleidoscopeEventSchema.index({ name: 'evtsrc_site_author_index', }); -module.exports = mongoose.model('KaleidoscopeEvent', KaleidoscopeEventSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('KaleidoscopeEvent', KaleidoscopeEventSchema); +}; \ No newline at end of file diff --git a/app/models/log.js b/app/models/log.js index b2462f6..b74d682 100644 --- a/app/models/log.js +++ b/app/models/log.js @@ -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); +}; \ No newline at end of file diff --git a/app/models/media-router.js b/app/models/media-router.js index c8dc32c..0416002 100644 --- a/app/models/media-router.js +++ b/app/models/media-router.js @@ -50,4 +50,6 @@ const MediaRouterSchema = new Schema({ } }); -module.exports = mongoose.model('MediaRouter', MediaRouterSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('MediaRouter', MediaRouterSchema); +}; \ No newline at end of file diff --git a/app/models/media-worker.js b/app/models/media-worker.js index feebc87..51b5e4c 100644 --- a/app/models/media-worker.js +++ b/app/models/media-worker.js @@ -40,4 +40,6 @@ const MediaWorkerSchema = new Schema({ } }); -module.exports = mongoose.model('MediaWorker', MediaWorkerSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('MediaWorker', MediaWorkerSchema); +}; \ No newline at end of file diff --git a/app/models/net-host-stats.js b/app/models/net-host-stats.js index 084068e..76f315a 100644 --- a/app/models/net-host-stats.js +++ b/app/models/net-host-stats.js @@ -72,4 +72,6 @@ const NetHostStatsSchema = new Schema({ network: { type: [NetworkInterfaceStatsSchema], required: true }, }); -module.exports = mongoose.model('NetHostStats', NetHostStatsSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('NetHostStats', NetHostStatsSchema); +}; \ No newline at end of file diff --git a/app/models/net-host.js b/app/models/net-host.js index 0646b2e..c8d482c 100644 --- a/app/models/net-host.js +++ b/app/models/net-host.js @@ -42,4 +42,6 @@ const NetHostSchema = new Schema({ network: { type: [NetworkInterfaceSchema] }, }); -module.exports = mongoose.model('NetHost', NetHostSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('NetHost', NetHostSchema); +}; \ No newline at end of file diff --git a/app/models/newsletter-recipient.js b/app/models/newsletter-recipient.js index da76741..2acfb65 100644 --- a/app/models/newsletter-recipient.js +++ b/app/models/newsletter-recipient.js @@ -18,4 +18,6 @@ const NewsletterRecipientSchema = new Schema({ }, }); -module.exports = mongoose.model('NewsletterRecipient', NewsletterRecipientSchema); +module.exports = (conn) => { + return conn.model('NewsletterRecipient', NewsletterRecipientSchema); +}; \ No newline at end of file diff --git a/app/models/newsletter.js b/app/models/newsletter.js index b036fdb..0f11675 100644 --- a/app/models/newsletter.js +++ b/app/models/newsletter.js @@ -28,4 +28,6 @@ const NewsletterSchema = new Schema({ }, }); -module.exports = mongoose.model('Newsletter', NewsletterSchema); +module.exports = (conn) => { + return conn.model('Newsletter', NewsletterSchema); +}; \ No newline at end of file diff --git a/app/models/oauth2-authorization-code.js b/app/models/oauth2-authorization-code.js index 2017ea7..4278c90 100644 --- a/app/models/oauth2-authorization-code.js +++ b/app/models/oauth2-authorization-code.js @@ -16,4 +16,6 @@ const OAuth2AuthorizationCodeSchema = new Schema({ scopes: { type: [String], required: true }, }); -module.exports = mongoose.model('OAuth2AuthorizationCode', OAuth2AuthorizationCodeSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OAuth2AuthorizationCode', OAuth2AuthorizationCodeSchema); +}; \ No newline at end of file diff --git a/app/models/oauth2-client.js b/app/models/oauth2-client.js index de56e24..e9d9877 100644 --- a/app/models/oauth2-client.js +++ b/app/models/oauth2-client.js @@ -40,4 +40,6 @@ OAuth2ClientSchema.index({ unique: true, }); -module.exports = mongoose.model('OAuth2Client', OAuth2ClientSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OAuth2Client', OAuth2ClientSchema); +}; \ No newline at end of file diff --git a/app/models/oauth2-token.js b/app/models/oauth2-token.js index f8133dd..692e279 100644 --- a/app/models/oauth2-token.js +++ b/app/models/oauth2-token.js @@ -24,4 +24,6 @@ OAuth2TokenSchema.index({ name: 'oauth2_token_unique', }); -module.exports = mongoose.model('OAuth2Token', OAuth2TokenSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OAuth2Token', OAuth2TokenSchema); +}; \ No newline at end of file diff --git a/app/models/otp-account.js b/app/models/otp-account.js index fcaea75..1bc1287 100644 --- a/app/models/otp-account.js +++ b/app/models/otp-account.js @@ -33,4 +33,6 @@ OtpAccountSchema.index({ name: 'otp_user_svc_uniq_idx', }); -module.exports = mongoose.model('OtpAccount', OtpAccountSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('OtpAccount', OtpAccountSchema); +}; \ No newline at end of file diff --git a/app/models/resource-view.js b/app/models/resource-view.js index d59ad0a..c2cfbd2 100644 --- a/app/models/resource-view.js +++ b/app/models/resource-view.js @@ -29,4 +29,6 @@ ResourceViewSchema.index({ name: 'res_view_daily_unique', }); -module.exports = mongoose.model('ResourceView', ResourceViewSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ResourceView', ResourceViewSchema); +}; \ No newline at end of file diff --git a/app/models/resource-visit.js b/app/models/resource-visit.js index 770ce64..1c36baf 100644 --- a/app/models/resource-visit.js +++ b/app/models/resource-visit.js @@ -26,4 +26,6 @@ ResourceVisitSchema.index({ name: 'resource_visits_for_user', }); -module.exports = mongoose.model('ResourceVisit', ResourceVisitSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('ResourceVisit', ResourceVisitSchema); +}; \ No newline at end of file diff --git a/app/models/sticker.js b/app/models/sticker.js index 5e33840..55dd4fe 100644 --- a/app/models/sticker.js +++ b/app/models/sticker.js @@ -43,4 +43,6 @@ const StickerSchema = new Schema({ encoded: { type: StickerMediaSchema }, }); -module.exports = mongoose.model('Sticker', StickerSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('Sticker', StickerSchema); +}; \ No newline at end of file diff --git a/app/models/user-block.js b/app/models/user-block.js index f6396e1..842c233 100644 --- a/app/models/user-block.js +++ b/app/models/user-block.js @@ -14,4 +14,6 @@ const UserBlockSchema = new Schema({ blockedMembers: { type: [DtpUserSchema] }, }); -module.exports = mongoose.model('UserBlock', UserBlockSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('UserBlock', UserBlockSchema); +}; \ No newline at end of file diff --git a/app/models/user-notification.js b/app/models/user-notification.js index 544de5f..fa5a0ff 100644 --- a/app/models/user-notification.js +++ b/app/models/user-notification.js @@ -24,4 +24,6 @@ const UserNotificationSchema = new Schema({ event: { type: Schema.ObjectId, required: true, ref: 'KaleidoscopeEvent' }, }); -module.exports = mongoose.model('UserNotification', UserNotificationSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('UserNotification', UserNotificationSchema); +}; \ No newline at end of file diff --git a/app/models/user-subscription.js b/app/models/user-subscription.js index 7200eea..d7bea99 100644 --- a/app/models/user-subscription.js +++ b/app/models/user-subscription.js @@ -24,4 +24,6 @@ const UserSubscriptionSchema = new Schema({ subscriptions: { type: [SubscriptionSchema] }, }); -module.exports = mongoose.model('UserSubscription', UserSubscriptionSchema); \ No newline at end of file +module.exports = (conn) => { + return conn.model('UserSubscription', UserSubscriptionSchema); +}; \ No newline at end of file diff --git a/app/models/user.js b/app/models/user.js index 5edef6c..343c671 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -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 }, @@ -62,4 +63,6 @@ UserSchema.virtual('hasAuthorDashboard').get( function ( ) { this.permissions.canPublishPosts; }); -module.exports = mongoose.model('User', UserSchema); +module.exports = (conn) => { + return conn.model('User', UserSchema); +}; \ No newline at end of file diff --git a/app/services/chat.js b/app/services/chat.js index 8a66e26..112b2b9 100644 --- a/app/services/chat.js +++ b/app/services/chat.js @@ -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'); } diff --git a/app/services/session.js b/app/services/session.js index b85074e..b56dc3a 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -92,8 +92,9 @@ class SessionService extends SiteService { delete user.stats._id; delete user.optIn._id; break; + case 'local': - user = await userService.getUserAccount(userId); + user = await userService.getLocalUserAccount(userId); user.type = 'User'; break; } diff --git a/app/services/user.js b/app/services/user.js index afba7f6..11caade 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -20,6 +20,12 @@ const uuidv4 = require('uuid').v4; const { SiteError, SiteService } = require('../../lib/site-lib'); +/* + * The entire concept of "get a user" is in flux right now. It's best to just + * ignore what's happening in this service right now, and focus on other + * features in the sytem. + */ + class UserService extends SiteService { constructor (dtp) { @@ -102,25 +108,25 @@ class UserService extends SiteService { user.password = maskedPassword; user.flags = { - isAdmin: userDefinition.isAdmin || false, - isModerator: userDefinition.isModerator || false, - isEmailVerified: userDefinition.isEmailVerified || false, + isAdmin: false, + isModerator: false, + isEmailVerified: false, }; user.permissions = { - canLogin: userDefinition.canLogin || true, - canChat: userDefinition.canChat || true, - canComment: userDefinition.canComment || true, - canReport: userDefinition.canReport || true, - canAuthorPosts: userDefinition.canAuthorPosts || false, - canAuthorPages: userDefinition.canAuthorPages || false, - canPublishPosts: userDefinition.canPublishPosts || false, - canPublishPages: userDefinition.canPublishPages || false, + canLogin: true, + canChat: true, + canComment: true, + canReport: true, + canAuthorPosts: false, + canAuthorPages: false, + canPublishPosts: false, + canPublishPages: false, }; user.optIn = { - system: userDefinition.optInSystem || true, - marketing: userDefinition.optInMarketing || false, + system: true, + marketing: false, }; this.log.info('creating new user account', { email: userDefinition.email }); @@ -184,7 +190,7 @@ class UserService extends SiteService { async emailOptOut (userId, category) { userId = mongoose.Types.ObjectId(userId); - const user = await this.getUserAccount(userId); + const user = await this.getLocalUserAccount(userId); if (!user) { throw new SiteError(406, 'Invalid opt-out token'); } @@ -209,7 +215,6 @@ class UserService extends SiteService { throw SiteError(403, 'Invalid user account operation'); } - // strip characters we don't want to allow in username userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); const username_lc = userDefinition.username.toLowerCase(); @@ -231,8 +236,7 @@ class UserService extends SiteService { ); } - async updateForAdmin (user, userDefinition) { - // strip characters we don't want to allow in username + async updateLocalForAdmin (user, userDefinition) { userDefinition.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); const username_lc = userDefinition.username.toLowerCase(); @@ -283,7 +287,6 @@ class UserService extends SiteService { const updateOp = { $set: { }, $unset: { } }; - // strip characters we don't want to allow in username updateOp.$set.username = striptags(userDefinition.username.trim().replace(/[^A-Za-z0-9\-_]/gi, '')); if (!updateOp.$set.username || (updateOp.$set.username.length === 0)) { throw new SiteError(400, 'Must include a username'); @@ -321,7 +324,7 @@ class UserService extends SiteService { }, options); const accountEmail = account.username.trim().toLowerCase(); - const accountUsername = await this.filterUsername(accountEmail); + const accountUsername = this.filterUsername(accountEmail); this.log.debug('locating user record', { accountEmail, accountUsername }); let user = await User @@ -469,28 +472,23 @@ class UserService extends SiteService { ); } - filterUserObject (user) { - const filteredUser = { - _id: user._id, - created: user.created, - displayName: user.displayName, - username: user.username, - username_lc: user.username_lc, - bio: user.bio, - flags: user.flags, - permissions: user.permissions, - picture: user.picture, - }; - if (filteredUser.flags && filteredUser.flags._id) { - delete filteredUser.flags._id; + async getLocalUserId (username) { + const user = await User.findOne({ username_lc: username }).select('_id').lean(); + if (!user) { + return; // undefined } - if (filteredUser.permissions && filteredUser.permissions._id) { - delete filteredUser.permissions._id; + return user._id; + } + + async getCoreUserId (username) { + const user = await CoreUser.findOne({ username_lc: username }).select('_id').lean(); + if (!user) { + return; // undefined } - return filteredUser; + return user._id; } - async getUserAccount (userId) { + async getLocalUserAccount (userId) { const user = await User .findById(userId) .select('+email +flags +permissions +optIn +picture') @@ -512,11 +510,47 @@ class UserService extends SiteService { user.hasAuthorDashboard = user.hasAuthorPermissions || user.hasPublishPermissions; } - async getUserAccounts (pagination, username) { + async getCoreUserAccount (userId) { + const user = await User + .findById(userId) + .select('+email +flags +permissions +optIn +picture') + .populate(this.populateUser) + .lean(); + if (!user) { + throw new SiteError(404, 'Core member account not found'); + } + user.type = 'CoreUser'; + return user; + } + + async getLocalUserProfile (userId) { + const user = await User + .findById(userId) + .select('+email +flags +settings') + .populate(this.populateUser) + .lean(); + user.type = 'User'; + return user; + } + + async getCoreUserProfile (userId) { + const user = await CoreUser + .findById(userId) + .select('+core +flags +settings') + .populate(this.populateUser) + .lean(); + user.type = 'CoreUser'; + return user; + } + + async searchLocalUserAccounts (pagination, username) { let search = { }; + if (username) { + username = this.filterUsername(username); search.username_lc = { $regex: `^${username.toLowerCase().trim()}` }; } + const users = await User .find(search) .sort({ username_lc: 1 }) @@ -529,60 +563,24 @@ class UserService extends SiteService { return users.map((user) => { user.type = 'User'; return user; }); } - async getUserProfile (userId) { - let user; - try { - userId = mongoose.Types.ObjectId(userId); // will throw if invalid format - user = User.findById(userId); - } catch (error) { - user = User.findOne({ username: userId }); - } - user = await user - .select('+email +flags +settings') - .populate(this.populateUser) - .lean(); - return user; - } - - async getPublicProfile (type, username) { - if (!username || (typeof username !== 'string')) { - throw new SiteError(406, 'Invalid username'); - } + async searchCoreUserAccounts (pagination, username) { + let search = { }; - username = username.trim().toLowerCase(); - if (username.length === 0) { - throw new SiteError(406, 'Invalid username'); + username = this.filterUsername(username); + if (username) { + search.username_lc = { $regex: `^${username.toLowerCase().trim()}` }; } - let user; - switch (type) { - case 'CoreUser': - user = await CoreUser - .findOne({ username_lc: username }) - .select('_id created username username_lc displayName bio picture header core') - .populate(this.populateUser) - .lean(); - if (user) { - user.type = 'CoreUser'; - } - break; - - case 'User': - user = await User - .findOne({ username_lc: username }) - .select('_id created username username_lc displayName bio picture header') - .populate(this.populateUser) - .lean(); - if (user) { - user.type = 'User'; - } - break; - - default: - throw new SiteError(400, 'Invalid user account type'); - } + const users = await CoreUser + .find(search) + .sort({ username_lc: 1 }) + .select('+core +coreUserId +flags +permissions +optIn') + .skip(pagination.skip) + .limit(pagination.cpp) + .lean() + ; - return user; + return users.map((user) => { user.type = 'CoreUser'; return user; }); } async getRecent (maxCount = 3) { @@ -664,10 +662,6 @@ class UserService extends SiteService { return actions; } - async filterUsername (username) { - return striptags(username.trim().toLowerCase()).replace(/\W/g, ''); - } - async checkUsername (username) { if (!username || (typeof username !== 'string') || (username.length === 0)) { throw new SiteError(406, 'Invalid username'); @@ -683,6 +677,34 @@ class UserService extends SiteService { } } + filterUsername (username) { + while (username[0] === '@') { + username = username.slice(1); + } + return striptags(username.trim().toLowerCase()).replace(/\W/g, ''); + } + + filterUserObject (user) { + const filteredUser = { + _id: user._id, + created: user.created, + displayName: user.displayName, + username: user.username, + username_lc: user.username_lc, + bio: user.bio, + flags: user.flags, + permissions: user.permissions, + picture: user.picture, + }; + if (filteredUser.flags && filteredUser.flags._id) { + delete filteredUser.flags._id; + } + if (filteredUser.permissions && filteredUser.permissions._id) { + delete filteredUser.permissions._id; + } + return filteredUser; + } + async recordProfileView (user, req) { const { resource: resourceService } = this.dtp.services; await resourceService.recordView(req, 'User', user._id); @@ -738,6 +760,41 @@ class UserService extends SiteService { await User.updateOne({ _id: user._id }, { $unset: { 'picture': '' } }); } + async updateHeaderImage (user, file) { + const { image: imageService } = this.dtp.services; + + await this.removeHeaderImage(user.header); + + const images = [ + { + width: 1400, + height: 400, + format: 'jpeg', + formatParameters: { + quality: 80, + }, + }, + ]; + await imageService.processImageFile(user, file, images); + await User.updateOne( + { _id: user._id }, + { + $set: { + 'header': images[0].image._id, + }, + }, + ); + } + + async removeHeaderImage (user) { + const { image: imageService } = this.dtp.services; + user = await this.getUserAccount(user._id); + if (user.header) { + await imageService.deleteImage(user.header); + } + await User.updateOne({ _id: user._id }, { $unset: { 'header': '' } }); + } + async blockUser (user, blockedUser) { if (user._id.equals(blockedUser._id)) { throw new SiteError(406, "You can't block yourself"); @@ -812,4 +869,4 @@ module.exports = { slug: 'user', name: 'user', create: (dtp) => { return new UserService(dtp); }, -}; +}; \ No newline at end of file diff --git a/app/views/admin/core-user/form.pug b/app/views/admin/core-user/form.pug index 5410689..4940134 100644 --- a/app/views/admin/core-user/form.pug +++ b/app/views/admin/core-user/form.pug @@ -13,7 +13,7 @@ block content if userAccount.displayName .uk-text-large= userAccount.displayName div - a(href=`/user/${userAccount._id}`) @#{userAccount.username} + a(href=`/user/${userAccount.username}`) @#{userAccount.username} .uk-card-body .uk-margin diff --git a/app/views/admin/user/form.pug b/app/views/admin/user/form.pug index 19d5d9a..b3ec608 100644 --- a/app/views/admin/user/form.pug +++ b/app/views/admin/user/form.pug @@ -5,7 +5,7 @@ block content div(uk-grid).uk-grid-small div(class="uk-width-1-1 uk-width-2-3@l") - form(method="POST", action=`/admin/user/${userAccount._id}`).uk-form + form(method="POST", action=`/admin/user/local/${userAccount._id}`).uk-form input(type="hidden", name="username", value= userAccount.username) input(type="hidden", name="displayName", value= userAccount.displayName) .uk-card.uk-card-default.uk-card-small @@ -20,7 +20,7 @@ block content .uk-width-auto a(href=`mailto:${userAccount.email}`)= userAccount.email .uk-width-auto - a(href=`/user/${userAccount._id}`) @#{userAccount.username} + a(href=`/user/${userAccount.username}`) @#{userAccount.username} .uk-card-body .uk-margin diff --git a/app/views/admin/user/index.pug b/app/views/admin/user/index.pug index 096bd13..66c17c2 100644 --- a/app/views/admin/user/index.pug +++ b/app/views/admin/user/index.pug @@ -22,10 +22,10 @@ block content each userAccount in userAccounts tr td - a(href=`/admin/user/${userAccount._id}`)= userAccount.username + a(href=`/admin/user/local/${userAccount._id}`)= userAccount.username td if userAccount.displayName - a(href=`/admin/user/${userAccount._id}`)= userAccount.displayName + a(href=`/admin/user/local/${userAccount._id}`)= userAccount.displayName else .uk-text-muted N/A td= moment(userAccount.created).format('YYYY-MM-DD hh:mm a') diff --git a/app/views/components/navbar.pug b/app/views/components/navbar.pug index 1cc2f51..b7c3d37 100644 --- a/app/views/components/navbar.pug +++ b/app/views/components/navbar.pug @@ -72,7 +72,7 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top i.fas.fa-user span Profile li - a(href= user.core ? `/user/core/${user._id}/settings` : `/user/${user._id}/settings`) + a(href= user.core ? `/user/core/${user.username}/settings` : `/user/${user.username}/settings`) span.nav-item-icon i.fas.fa-cog span Settings diff --git a/app/views/components/off-canvas.pug b/app/views/components/off-canvas.pug index 689900e..ef2ddc5 100644 --- a/app/views/components/off-canvas.pug +++ b/app/views/components/off-canvas.pug @@ -68,7 +68,7 @@ mixin renderMenuItem (iconClass, label) .uk-width-expand Profile li(class={ "uk-active": (currentView === 'user-settings') }) - a(href=`/user/${user._id}/settings`).uk-display-block + a(href=`/user/${user.username}/settings`).uk-display-block div(uk-grid).uk-grid-collapse .uk-width-auto .app-menu-icon diff --git a/client/less/site/markdown.less b/client/less/site/markdown.less new file mode 100644 index 0000000..5a9c621 --- /dev/null +++ b/client/less/site/markdown.less @@ -0,0 +1,9 @@ +.markdown-block { + font-size: @global-font-size; + line-height: @global-line-height; + color: @global-color; + + p:last-of-type { + margin-bottom: 0; + } +} \ No newline at end of file diff --git a/client/less/style.common.less b/client/less/style.common.less index 86c5642..9e2764e 100644 --- a/client/less/style.common.less +++ b/client/less/style.common.less @@ -10,13 +10,14 @@ @import "site/kaleidoscope-event.less"; @import "site/nav.less"; +@import "site/button.less"; @import "site/content.less"; @import "site/core-node.less"; @import "site/dashboard.less"; -@import "site/site.less"; @import "site/form.less"; -@import "site/button.less"; -@import "site/sidebar.less"; +@import "site/markdown.less"; @import "site/section.less"; +@import "site/sidebar.less"; +@import "site/site.less"; @import "site/chat.less"; \ No newline at end of file diff --git a/dtp-sites-cli.js b/dtp-sites-cli.js index f4ff334..116f423 100644 --- a/dtp-sites-cli.js +++ b/dtp-sites-cli.js @@ -34,6 +34,10 @@ module.grantPermission = async (target, permission) => { const User = mongoose.model('User'); try { const user = await User.findOne({ email: target }).select('+permissions +flags'); + if (!user) { + throw new Error(`User not found (email: ${target})`); + } + switch (permission) { case 'admin': user.flags.isAdmin = true; diff --git a/dtp-sites.js b/dtp-sites.js index ec36753..6f03d3e 100644 --- a/dtp-sites.js +++ b/dtp-sites.js @@ -43,7 +43,7 @@ module.config = { module.log = new SiteLog(module, module.config.component); module.shutdown = async ( ) => { - await SitePlatform.shutdown(); + return await SitePlatform.shutdown(); }; (async ( ) => { @@ -63,8 +63,8 @@ module.shutdown = async ( ) => { process.once('SIGINT', async ( ) => { module.log.info('SIGINT received'); module.log.info('requesting shutdown...'); - await module.shutdown(); - const exitCode = await SitePlatform.shutdown(); + const exitCode = await module.shutdown(); + process.nextTick(( ) => { process.exit(exitCode); }); diff --git a/lib/client/js/dtp-log.js b/lib/client/js/dtp-log.js index 6ee4c4f..1746cc6 100644 --- a/lib/client/js/dtp-log.js +++ b/lib/client/js/dtp-log.js @@ -1,4 +1,4 @@ -// dtpweb-log.js +// dtp-log.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 diff --git a/lib/client/js/dtp-socket.js b/lib/client/js/dtp-socket.js index 95a4a88..be63d47 100644 --- a/lib/client/js/dtp-socket.js +++ b/lib/client/js/dtp-socket.js @@ -1,4 +1,4 @@ -// dtpweb-socket.js +// dtp-socket.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 diff --git a/lib/site-ioserver.js b/lib/site-ioserver.js index eea3471..bfcbae1 100644 --- a/lib/site-ioserver.js +++ b/lib/site-ioserver.js @@ -15,12 +15,12 @@ const ConnectToken = mongoose.model('ConnectToken'); const marked = require('marked'); const { SiteLog } = require(path.join(__dirname, 'site-log')); +const { SiteCommon } = require(path.join(__dirname, 'site-common')); -const Events = require('events'); -class SiteIoServer extends Events { +class SiteIoServer extends SiteCommon { constructor (dtp) { - super(); + super(dtp, { name: 'ioServer', slug: 'io-server' }); this.dtp = dtp; this.log = new SiteLog(dtp, DTP_COMPONENT); } @@ -74,6 +74,10 @@ class SiteIoServer extends Events { } async stop ( ) { + if (this.io) { + this.io.close(); + delete this.io; + } } diff --git a/lib/site-platform.js b/lib/site-platform.js index 3b390d0..50ad0e4 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -34,12 +34,22 @@ module.connectDatabase = async (/*dtp*/) => { host: process.env.MONGODB_HOST, database: process.env.MONGODB_DATABASE, }); - const mongoConnectUri = `mongodb://${process.env.MONGODB_HOST}/${process.env.MONGODB_DATABASE}`; + const mongoConnectionInfo = { + host: process.env.MONGODB_HOST, + db: process.env.MONGODB_DATABASE, + username: encodeURIComponent(process.env.MONGODB_USERNAME), + password: encodeURIComponent(process.env.MONGODB_PASSWORD), + options: process.env.MONGODB_OPTIONS || '', + }; + let mongoConnectUri = `mongodb://${process.env.MONGODB_HOST}/${process.env.MONGODB_DATABASE}`; + if (process.env.NODE_ENV === 'production'){ + mongoConnectUri = `mongodb://${mongoConnectionInfo.username}:${mongoConnectionInfo.password}@${mongoConnectionInfo.host}/${mongoConnectionInfo.options}`; + } module.db = await mongoose.connect(mongoConnectUri, { socketTimeoutMS: 0, keepAlive: true, keepAliveInitialDelay: 300000, - dbName: process.env.MONGODB_DATABASE, + dbName: mongoConnectionInfo.db, }); module.log.info('connected to MongoDB'); } catch (error) { @@ -48,12 +58,12 @@ module.connectDatabase = async (/*dtp*/) => { } }; - module.loadModels = async (dtp) => { dtp.models = module.models = [ ]; const modelScripts = glob.sync(path.join(dtp.config.root, 'app', 'models', '*.js')); modelScripts.forEach((modelScript) => { - const model = require(modelScript); + const instance = require(modelScript); + const model = instance(module.db); if (module.models[model.modelName]) { module.log.error('model name collision', { name: model.modelName }); process.exit(-1); @@ -146,7 +156,7 @@ module.loadControllers = async (dtp) => { await SiteAsync.each(scripts, async (script) => { const controller = await require(script); controller.instance = await controller.create(dtp); - + module.log.info('controller loaded', { name: controller.name, slug: controller.slug }); dtp.controllers[controller.name] = controller; inits.push(controller); @@ -196,7 +206,7 @@ module.exports.startPlatform = async (dtp) => { await module.connectRedis(dtp); await module.loadModels(dtp); - SiteLog.setModel(mongoose.model('Log')); + SiteLog.setModel(module.db.model('Log')); await module.loadServices(dtp);