// 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.navigateTo(`/chat/room/${res.locals.invite.room._id}`); break; case 'reject': await chatService.rejectRoomInvite(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); }, };