146 changed files with 6861 additions and 1091 deletions
@ -0,0 +1,413 @@ |
|||
// email.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const express = require('express'); |
|||
|
|||
const { SiteController,/*, SiteError*/ |
|||
SiteError} = require('../../lib/site-lib'); |
|||
|
|||
class ChatController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const { |
|||
chat: chatService, |
|||
limiter: limiterService, |
|||
session: sessionService, |
|||
} = this.dtp.services; |
|||
|
|||
const upload = this.createMulter(); |
|||
|
|||
const router = express.Router(); |
|||
this.dtp.app.use('/chat', router); |
|||
|
|||
router.use( |
|||
sessionService.authCheckMiddleware({ requireLogin: true }), |
|||
chatService.middleware({ maxOwnedRooms: 25, maxJoinedRooms: 50 }), |
|||
async (req, res, next) => { |
|||
res.locals.currentView = 'chat'; |
|||
return next(); |
|||
}, |
|||
); |
|||
|
|||
router.param('roomId', this.populateRoomId.bind(this)); |
|||
router.param('inviteId', this.populateInviteId.bind(this)); |
|||
|
|||
router.post( |
|||
'/room/:roomId/invite/:inviteId/action', |
|||
limiterService.createMiddleware(limiterService.config.chat.postRoomInviteAction), |
|||
upload.none(), |
|||
this.postRoomInviteAction.bind(this), |
|||
); |
|||
|
|||
router.post( |
|||
'/room/:roomId/invite', |
|||
limiterService.createMiddleware(limiterService.config.chat.postRoomInvite), |
|||
upload.none(), |
|||
this.postRoomInvite.bind(this), |
|||
); |
|||
|
|||
router.post( |
|||
'/room/:roomId', |
|||
limiterService.createMiddleware(limiterService.config.chat.postRoomUpdate), |
|||
this.postRoomUpdate.bind(this), |
|||
); |
|||
|
|||
router.post( |
|||
'/room', |
|||
limiterService.createMiddleware(limiterService.config.chat.postRoomCreate), |
|||
this.postRoomCreate.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room/create', |
|||
this.getRoomEditor.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room/:roomId/form/:formName', |
|||
limiterService.createMiddleware(limiterService.config.chat.getRoomForm), |
|||
this.getRoomForm.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room/:roomId/invite/:inviteId', |
|||
limiterService.createMiddleware(limiterService.config.chat.getRoomInviteView), |
|||
this.getRoomInviteView.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room/:roomId/invite', |
|||
limiterService.createMiddleware(limiterService.config.chat.getRoomInviteView), |
|||
this.getRoomInviteHome.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room/:roomId/settings', |
|||
limiterService.createMiddleware(limiterService.config.chat.getRoomSettings), |
|||
this.getRoomSettings.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room/:roomId', |
|||
limiterService.createMiddleware(limiterService.config.chat.getRoomView), |
|||
this.getRoomView.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/room', |
|||
limiterService.createMiddleware(limiterService.config.chat.getRoomHome), |
|||
this.getRoomHome.bind(this), |
|||
); |
|||
|
|||
router.get( |
|||
'/', |
|||
limiterService.createMiddleware(limiterService.config.chat.getHome), |
|||
this.getHome.bind(this), |
|||
); |
|||
|
|||
/* |
|||
* DELETE operations |
|||
*/ |
|||
|
|||
router.delete( |
|||
'/room/:roomId/invite/:inviteId', |
|||
limiterService.createMiddleware(limiterService.config.chat.deleteInvite), |
|||
this.deleteInvite.bind(this), |
|||
); |
|||
|
|||
router.delete( |
|||
'/room/:roomId', |
|||
limiterService.createMiddleware(limiterService.config.chat.deleteRoom), |
|||
this.deleteInvite.bind(this), |
|||
); |
|||
|
|||
return router; |
|||
} |
|||
|
|||
async populateRoomId (req, res, next, roomId) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
res.locals.room = await chatService.getRoomById(roomId); |
|||
if (!res.locals.room) { |
|||
throw new SiteError(404, 'Room not found'); |
|||
} |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate roomId', { roomId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async populateInviteId (req, res, next, inviteId) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
res.locals.invite = await chatService.getRoomInviteById(inviteId); |
|||
if (!res.locals.invite) { |
|||
throw new SiteError(404, 'Invite not found'); |
|||
} |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate inviteId', { inviteId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async postRoomInviteAction (req, res) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
const { response } = req.body; |
|||
const displayList = this.createDisplayList('room-invite-action'); |
|||
this.log.debug('room invite action', { message: req.body }); |
|||
switch (response) { |
|||
case 'accept': |
|||
await chatService.acceptRoomInvite(res.locals.invite); |
|||
displayList.showNotification( |
|||
`Chat room invite accepted`, |
|||
'success', |
|||
'top-center', |
|||
5000, |
|||
); |
|||
break; |
|||
|
|||
case 'reject': |
|||
await chatService.acceptRoomInvite(res.locals.invite); |
|||
displayList.showNotification( |
|||
`Chat room invite rejected`, |
|||
'success', |
|||
'top-center', |
|||
5000, |
|||
); |
|||
break; |
|||
|
|||
default: |
|||
throw new SiteError(400, 'Must specify invite action'); |
|||
} |
|||
|
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to execute room invite action', { |
|||
inviteId: res.locals.invite._id, |
|||
response: req.body.response, |
|||
error, |
|||
}); |
|||
return res.status(error.statusCode || 500).json({ |
|||
success: false, |
|||
message: error.message, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
async postRoomInvite (req, res) { |
|||
const { chat: chatService, 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' }); |
|||
} |
|||
try { |
|||
req.body.username = req.body.username.trim().toLowerCase(); |
|||
while (req.body.username[0] === '@') { |
|||
req.body.username = req.body.username.slice(1); |
|||
} |
|||
|
|||
if (!req.body.username || !req.body.username.length) { |
|||
throw new SiteError(400, 'Please provide a username'); |
|||
} |
|||
|
|||
const member = await userService.getPublicProfile(req.body.username); |
|||
if (!member) { |
|||
throw new SiteError(404, `There is no account with username @${req.body.username}`); |
|||
} |
|||
if (member._id.equals(res.locals.room.owner._id)) { |
|||
throw new SiteError(400, "You can't invite yourself."); |
|||
} |
|||
|
|||
await chatService.sendRoomInvite(res.locals.room, member, req.body); |
|||
|
|||
const displayList = this.createDisplayList('invite create'); |
|||
displayList.showNotification( |
|||
`Chat room invite sent to ${member.displayName || member.username}!`, |
|||
'success', |
|||
'top-left', |
|||
5000, |
|||
); |
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to create room invitation', { error }); |
|||
return res.status(error.statusCode || 500).json({ |
|||
success: false, |
|||
message: error.message, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
async postRoomUpdate (req, res, next) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
res.locals.room = await chatService.updateRoom(res.locals.room, req.body); |
|||
res.redirect(`/chat/room/${res.locals.room._id}`); |
|||
} catch (error) { |
|||
this.log.error('failed to update chat room', { |
|||
// roomId: res.locals.room._id,
|
|||
error, |
|||
}); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async postRoomCreate (req, res, next) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
res.locals.room = await chatService.createRoom(req.user, req.body); |
|||
res.redirect(`/chat/room/${res.locals.room._id}`); |
|||
} catch (error) { |
|||
this.log.error('failed to create chat room', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getRoomEditor (req, res) { |
|||
res.render('chat/room/editor'); |
|||
} |
|||
|
|||
async getRoomForm (req, res, next) { |
|||
const validFormNames = [ |
|||
'invite-member', |
|||
]; |
|||
const formName = req.params.formName; |
|||
if (validFormNames.indexOf(formName) === -1) { |
|||
return next(new SiteError(404, 'Form not found')); |
|||
} |
|||
try { |
|||
res.render(`chat/room/form/${formName}`); |
|||
} catch (error) { |
|||
this.log.error('failed to render form', { formName, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getRoomInviteView (req, res) { |
|||
res.render('chat/room/invite/view'); |
|||
} |
|||
|
|||
async getRoomInviteHome (req, res, next) { |
|||
const { chat: chatService } = 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'), |
|||
}; |
|||
res.render('chat/room/invite'); |
|||
} catch (error) { |
|||
this.log.error('failed to render the room invites view', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getRoomSettings (req, res) { |
|||
res.render('chat/room/editor'); |
|||
} |
|||
|
|||
async getRoomView (req, res, next) { |
|||
const { chat: chatService } = 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); |
|||
|
|||
res.render('chat/room/view'); |
|||
} catch (error) { |
|||
this.log.error('failed to render chat room view', { roomId: req.params.roomId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getRoomHome (req, res, next) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
res.locals.pagination = this.getPaginationParameters(req, 20); |
|||
res.locals.publicRooms = await chatService.getPublicRooms(req.user, res.locals.pagination); |
|||
res.render('chat/room/index'); |
|||
} catch (error) { |
|||
this.log.error('failed to render room home', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getHome (req, res, next) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
res.locals.pageTitle = 'Chat Home'; |
|||
|
|||
res.locals.pagination = this.getPaginationParameters(req, 20); |
|||
|
|||
const roomIds = [ ]; |
|||
res.locals.ownedChatRooms.forEach((room) => roomIds.push(room._id)); |
|||
res.locals.joinedChatRooms.forEach((room) => roomIds.push(room._id)); |
|||
res.locals.timeline = await chatService.getMultiRoomTimeline(roomIds, res.locals.pagination); |
|||
|
|||
res.render('chat/index'); |
|||
} catch (error) { |
|||
this.log.error('failed to render chat home', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async deleteInvite (req, res, next) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
if (res.locals.room.owner._id.equals(req.user._id)) { |
|||
throw new SiteError(403, 'This is not your invitiation'); |
|||
} |
|||
|
|||
await chatService.deleteRoomInvite(res.locals.invite); |
|||
|
|||
const displayList = this.createDisplayList('delete chat invite'); |
|||
displayList.removeElement(`li[data-invite-id="${res.locals.invite._id}"]`); |
|||
displayList.showNotification( |
|||
`Invitation to ${res.locals.invite.member.displayName || res.locals.invite.member.username} deleted successfully`, |
|||
'success', |
|||
'top-left', |
|||
5000, |
|||
); |
|||
|
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to delete chat room invite', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async deleteRoom (req, res, next) { |
|||
const { chat: chatService } = this.dtp.services; |
|||
try { |
|||
if (res.locals.room.owner._id.equals(req.user._id)) { |
|||
throw new SiteError(403, 'This is not your chat room'); |
|||
} |
|||
|
|||
await chatService.deleteRoom(res.locals.room); |
|||
|
|||
const displayList = this.createDisplayList('delete chat invite'); |
|||
displayList.navigateTo('/chat'); |
|||
|
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to delete chat room invite', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'chat', |
|||
name: 'chat', |
|||
create: async (dtp) => { return new ChatController(dtp); }, |
|||
}; |
@ -0,0 +1,157 @@ |
|||
// comment.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const express = require('express'); |
|||
const numeral = require('numeral'); |
|||
|
|||
const { SiteController, SiteError } = require('../../lib/site-lib'); |
|||
|
|||
class CommentController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const { dtp } = this; |
|||
const { limiter: limiterService, session: sessionService } = dtp.services; |
|||
|
|||
const authRequired = sessionService.authCheckMiddleware({ requiredLogin: true }); |
|||
|
|||
const router = express.Router(); |
|||
dtp.app.use('/comment', router); |
|||
|
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = module.exports.slug; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('commentId', this.populateCommentId.bind(this)); |
|||
|
|||
router.post('/:commentId/vote', authRequired, this.postVote.bind(this)); |
|||
|
|||
router.get('/:commentId/replies', this.getCommentReplies.bind(this)); |
|||
|
|||
router.delete('/:commentId', |
|||
authRequired, |
|||
limiterService.createMiddleware(limiterService.config.comment.deleteComment), |
|||
this.deleteComment.bind(this), |
|||
); |
|||
} |
|||
|
|||
async populateCommentId (req, res, next, commentId) { |
|||
const { comment: commentService } = this.dtp.services; |
|||
try { |
|||
res.locals.comment = await commentService.getById(commentId); |
|||
if (!res.locals.comment) { |
|||
return next(new SiteError(404, 'Comment not found')); |
|||
} |
|||
res.locals.post = res.locals.comment.resource; |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate commentId', { commentId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async postVote (req, res) { |
|||
const { contentVote: contentVoteService } = this.dtp.services; |
|||
try { |
|||
const displayList = this.createDisplayList('comment-vote'); |
|||
const { message, resourceStats } = await contentVoteService.recordVote(req.user, 'Comment', res.locals.comment, req.body.vote); |
|||
displayList.setTextContent( |
|||
`button[data-comment-id="${res.locals.comment._id}"][data-vote="up"] span.dtp-item-value`, |
|||
numeral(resourceStats.upvoteCount).format(resourceStats.upvoteCount > 1000 ? '0,0.0a' : '0,0'), |
|||
); |
|||
displayList.setTextContent( |
|||
`button[data-comment-id="${res.locals.comment._id}"][data-vote="down"] span.dtp-item-value`, |
|||
numeral(resourceStats.downvoteCount).format(resourceStats.upvoteCount > 1000 ? '0,0.0a' : '0,0'), |
|||
); |
|||
displayList.showNotification(message, 'success', 'bottom-center', 3000); |
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to process comment vote', { error }); |
|||
return res.status(error.statusCode || 500).json({ |
|||
success: false, |
|||
message: error.message, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
async getCommentReplies (req, res) { |
|||
const { comment: commentService } = this.dtp.services; |
|||
try { |
|||
const displayList = this.createDisplayList('get-replies'); |
|||
|
|||
if (req.query.buttonId) { |
|||
displayList.removeElement(`li.dtp-load-more[data-button-id="${req.query.buttonId}"]`); |
|||
} |
|||
|
|||
Object.assign(res.locals, req.app.locals); |
|||
|
|||
res.locals.countPerPage = parseInt(req.query.cpp || "20", 10); |
|||
if (res.locals.countPerPage < 1) { |
|||
res.locals.countPerPage = 1; |
|||
} |
|||
if (res.locals.countPerPage > 20) { |
|||
res.locals.countPerPage = 20; |
|||
} |
|||
|
|||
res.locals.pagination = this.getPaginationParameters(req, res.locals.countPerPage); |
|||
res.locals.comments = await commentService.getReplies(res.locals.comment, res.locals.pagination); |
|||
|
|||
const html = await commentService.renderTemplate('replyList', res.locals); |
|||
|
|||
const replyList = `ul.dtp-reply-list[data-comment-id="${res.locals.comment._id}"]`; |
|||
displayList.addElement(replyList, 'beforeEnd', html); |
|||
|
|||
const replyListContainer = `.dtp-reply-list-container[data-comment-id="${res.locals.comment._id}"]`; |
|||
displayList.removeAttribute(replyListContainer, 'hidden'); |
|||
|
|||
if (Array.isArray(res.locals.comments) && (res.locals.comments.length > 0)) { |
|||
displayList.removeElement(`p#empty-comments-label[data-comment-id="${res.locals.comment._id}"]`); |
|||
} |
|||
|
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to display comment replies', { error }); |
|||
res.status(error.statusCode || 500).json({ success: false, message: error.message }); |
|||
} |
|||
} |
|||
|
|||
async deleteComment (req, res) { |
|||
const { comment: commentService } = this.dtp.services; |
|||
try { |
|||
const displayList = this.createDisplayList('add-recipient'); |
|||
|
|||
await commentService.remove(res.locals.comment, 'removed'); |
|||
|
|||
let selector = `article[data-comment-id="${res.locals.comment._id}"] .comment-content`; |
|||
displayList.setTextContent(selector, 'Comment removed'); |
|||
|
|||
displayList.showNotification( |
|||
'Comment removed successfully', |
|||
'success', |
|||
'bottom-center', |
|||
5000, |
|||
); |
|||
|
|||
res.status(200).json({ success: true, displayList }); |
|||
} catch (error) { |
|||
this.log.error('failed to remove comment', { error }); |
|||
return res.status(error.statusCode || 500).json({ |
|||
success: false, |
|||
message: error.message |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'comment', |
|||
name: 'comment', |
|||
create: async (dtp) => { return new CommentController(dtp); }, |
|||
}; |
@ -0,0 +1,70 @@ |
|||
// email.js
|
|||
// Copyright (C) 2021 Digital Telepresence, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const path = require('path'); |
|||
const glob = require('glob'); |
|||
|
|||
const express = require('express'); |
|||
|
|||
const { SiteController,/*, SiteError*/ |
|||
SiteError} = require('../../lib/site-lib'); |
|||
|
|||
class FormController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const { |
|||
chat: chatService, |
|||
limiter: limiterService, |
|||
session: sessionService, |
|||
} = this.dtp.services; |
|||
|
|||
try { |
|||
this.forms = glob.sync(path.join(this.dtp.config.root, 'app', 'views', 'form', '*pug')) || [ ]; |
|||
this.forms = this.forms.map((filename) => path.parse(filename)); |
|||
} catch (error) { |
|||
this.log.error('failed to detect requestable forms', { error }); |
|||
this.forms = [ ]; |
|||
// fall through
|
|||
} |
|||
|
|||
const router = express.Router(); |
|||
this.dtp.app.use('/form', router); |
|||
|
|||
router.use( |
|||
sessionService.authCheckMiddleware({ requireLogin: true }), |
|||
chatService.middleware({ maxOwnedRooms: 25, maxJoinedRooms: 50 }), |
|||
async (req, res, next) => { |
|||
res.locals.currentView = module.exports.slug; |
|||
return next(); |
|||
}, |
|||
); |
|||
|
|||
router.get( |
|||
'/:formSlug', |
|||
limiterService.createMiddleware(limiterService.config.form.getForm), |
|||
this.getForm.bind(this), |
|||
); |
|||
} |
|||
|
|||
async getForm (req, res, next) { |
|||
const { formSlug } = req.params; |
|||
const form = this.forms.find((form) => form.name === formSlug); |
|||
if (!form) { |
|||
return next(new SiteError(400, 'Invalid form')); |
|||
} |
|||
res.render(`form/${form.name}`); |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'form', |
|||
name: 'form', |
|||
create: async (dtp) => { return new FormController(dtp); }, |
|||
}; |
@ -0,0 +1,78 @@ |
|||
// notification.js
|
|||
// Copyright (C) 2022 DTP Technologies, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const express = require('express'); |
|||
|
|||
const { SiteController, SiteError } = require('../../lib/site-lib'); |
|||
|
|||
class NotificationController extends SiteController { |
|||
|
|||
constructor (dtp) { |
|||
super(dtp, module.exports); |
|||
} |
|||
|
|||
async start ( ) { |
|||
const { dtp } = this; |
|||
const { limiter: limiterService } = dtp.services; |
|||
|
|||
const router = express.Router(); |
|||
dtp.app.use('/notification', router); |
|||
|
|||
router.use(async (req, res, next) => { |
|||
res.locals.currentView = 'notification'; |
|||
return next(); |
|||
}); |
|||
|
|||
router.param('notificationId', this.populateNotificationId.bind(this)); |
|||
|
|||
router.get( |
|||
'/:notificationId', |
|||
limiterService.createMiddleware(limiterService.config.notification.getNotificationView), |
|||
this.getNotificationView.bind(this), |
|||
); |
|||
|
|||
router.get('/', |
|||
limiterService.createMiddleware(limiterService.config.notification.getNotificationHome), |
|||
this.getNotificationHome.bind(this), |
|||
); |
|||
} |
|||
|
|||
async populateNotificationId (req, res, next, notificationId) { |
|||
const { userNotification: userNotificationService } = this.dtp.services; |
|||
try { |
|||
res.locals.notification = await userNotificationService.getById(notificationId); |
|||
if (!res.locals.notification) { |
|||
throw new SiteError(404, 'Notification not found'); |
|||
} |
|||
return next(); |
|||
} catch (error) { |
|||
this.log.error('failed to populate notificationId', { notificationId, error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
|
|||
async getNotificationView (req, res) { |
|||
res.render('notification/view'); |
|||
} |
|||
|
|||
async getNotificationHome (req, res, next) { |
|||
const { userNotification: userNotificationService } = this.dtp.services; |
|||
try { |
|||
res.locals.pagination = this.getPaginationParameters(req, 20); |
|||
res.locals.notifications = await userNotificationService.getForUser(req.user, res.locals.pagination); |
|||
res.render('notification/index'); |
|||
} catch (error) { |
|||
this.log.error('failed to render notification home view', { error }); |
|||
return next(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
slug: 'notification', |
|||
name: 'notification', |
|||
create: async (dtp) => { return new NotificationController(dtp); }, |
|||
}; |
@ -0,0 +1,64 @@ |
|||
// attachment.js
|
|||
// Copyright (C) 2022 DTP Technologies, LLC
|
|||
// License: Apache-2.0
|
|||
|
|||
'use strict'; |
|||
|
|||
const mongoose = require('mongoose'); |
|||
|
|||
const Schema = mongoose.Schema; |
|||
|
|||
const ATTACHMENT_STATUS_LIST = [ |
|||
'processing', // the attachment is in the processing queue
|
|||
'live', // the attachment is available for use
|
|||
'rejected', // the attachment was rejected (by proccessing queue)
|
|||
'retired', // the attachment has been retired
|
|||
]; |
|||
|
|||
const AttachmentFileSchema = new Schema({ |
|||
bucket: { type: String, required: true }, |
|||
key: { type: String, required: true }, |
|||
mime: { type: String, required: true }, |
|||
size: { type: Number, required: true }, |
|||
etag: { type: String, required: true }, |
|||
}, { |
|||
_id: false, |
|||
}); |
|||
|
|||
/* |
|||
* Attachments are simply files. They can really be any kind of file, but will |
|||
* mostly be image, video, and audio files. |
|||
* |
|||
* owner is the User or CoreUser that uploaded the attachment. |
|||
* |
|||
* item is the item to which the attachment is attached. |
|||
*/ |
|||
const AttachmentSchema = new Schema({ |
|||
created: { type: Date, default: Date.now, required: true, index: 1 }, |
|||
status: { type: String, enum: ATTACHMENT_STATUS_LIST, default: 'processing', required: true }, |
|||
ownerType: { type: String, required: true }, |
|||
owner: { type: Schema.ObjectId, required: true, index: 1, refPath: 'ownerType' }, |
|||
itemType: { type: String, required: true }, |
|||
item: { type: Schema.ObjectId, required: true, index: 1, refPath: 'itemType' }, |
|||
original: { type: AttachmentFileSchema, required: true, select: false }, |
|||
encoded: { type: AttachmentFileSchema, required: true }, |
|||
flags: { |
|||
isSensitive: { type: Boolean, default: false, required: true }, |
|||
}, |
|||
}); |
|||
|
|||
AttachmentSchema.index({ |
|||
ownerType: 1, |
|||
owner: 1, |
|||
}, { |
|||
name: 'attachment_owner_idx', |
|||
}); |
|||
|
|||
AttachmentSchema.index({ |
|||
itemType: 1, |
|||
item: 1, |
|||
}, { |
|||
|