// auth.js // Copyright (C) 2024 DTP Technologies, LLC // All Rights Reserved 'use strict'; import express from 'express'; import { SiteController, SiteError } from '../../lib/site-lib.js'; export default class ChatController extends SiteController { static get name ( ) { return 'ChatController'; } static get slug ( ) { return 'chat'; } static get MESSAGES_PER_PAGE ( ) { return 20; } constructor (dtp) { super(dtp, ChatController); } async start ( ) { const { // csrfToken: csrfTokenService, // limiter: limiterService, session: sessionService, } = this.dtp.services; const authRequired = sessionService.authCheckMiddleware({ requireLogin: true }); const multer = this.createMulter(ChatController.slug); async function requireRoomOwner (req, res, next) { if (!req.user) { return next(new SiteError(403, 'Must be logged in to proceed')); } if (!res.locals.room) { return next(new SiteError(403, 'Room not found')); } if (!res.locals.room.owner._id.equals(req.user._id)) { return next(new SiteError(403, 'This is not your room')); } return next(); } const router = express.Router(); this.dtp.app.use('/chat', router); router.use( async (req, res, next) => { res.locals.currentView = 'auth'; return next(); }, authRequired, ); router.param('roomId', this.populateRoomId.bind(this)); router.param('messageId', this.populateMessageId.bind(this)); router.post( '/room/:roomId/message', // limiterService.create(limiterService.config.chat.postRoomMessage), multer.fields([{ name: 'imageFiles', maxCount: 6 }, { name: 'videoFiles', maxCount: 1 }]), this.postRoomMessage.bind(this), ); router.post( '/room/:roomId/settings', requireRoomOwner, // limiterService.create(limiterService.config.chat.postRoomSettings), this.postRoomSettings.bind(this), ); router.post( '/room', // limiterService.create(limiterService.config.chat.postCreateRoom), this.postCreateRoom.bind(this), ); router.post( '/message/:messageId/reaction', // limiterService.create(limiterService.config.chat.postMessageReaction), multer.none(), this.postMessageReaction.bind(this), ); router.get( '/room/create', // limiterService.create(limiterService.config.chat.getRoomCreateView), this.getRoomCreateView.bind(this), ); router.get( '/room/:roomId/join', // limiterService.create(limiterService.config.chat.getRoomView), this.getRoomJoin.bind(this), ); router.get( '/room/:roomId/messages', // limiterService.create(limiterService.config.chat.getRoomMessages), this.getRoomMessages.bind(this), ); router.get( '/room/:roomId/settings', // limiterService.create(limiterService.config.chat.getRoomMessages), requireRoomOwner, this.getRoomSettingsView.bind(this), ); router.get( '/room/:roomId', // limiterService.create(limiterService.config.chat.getRoomView), this.getRoomView.bind(this), ); router.delete( '/message/:messageId', // limiterService.create(limiterService.config.chat.deleteChatMessage), this.deleteChatMessage.bind(this), ); router.delete( '/room/:roomId', // limiterService.create(limiterService.config.chat.deleteRoom), this.deleteRoom.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, "The chat room doesn't exist."); } return next(); } catch (error) { return next(error); } } async populateMessageId (req, res, next, messageId) { const { chat: chatService } = this.dtp.services; try { res.locals.message = await chatService.getMessageById(messageId); if (!res.locals.message) { throw new SiteError(404, "The chat message doesn't exist."); } return next(); } catch (error) { return next(error); } } async postMessageReaction (req, res) { const { chat: chatService } = this.dtp.services; try { await chatService.toggleMessageReaction(req.user, res.locals.message, req.body); return res.status(200).json({ success: true }); } catch (error) { this.log.error('failed to send chat room message', { error }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } async postRoomMessage (req, res) { const { chat: chatService } = this.dtp.services; try { this.log.debug('post attachments', { imageFiles: req.files.imageFiles, videoFiles: req.files.videoFiles, }); await chatService.sendRoomMessage( res.locals.room, req.user, req.body, req.files.imageFiles, req.files.videoFiles, ); return res.status(200).json({ success: true }); } catch (error) { this.log.error('failed to send chat room message', { error }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } async postRoomSettings (req, res, next) { const { chat: chatService } = this.dtp.services; try { await chatService.updateRoomSettings(res.locals.room, req.body); res.redirect(`/chat/room/${res.locals.room._id}`); } catch (error) { this.log.error('failed to present the room settings view', { error }); return next(error); } } async postCreateRoom (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) { return next(error); } } async getRoomCreateView (req, res) { res.render('chat/room/create'); } async getRoomJoin (req, res, next) { const { chat: chatService } = this.dtp.services; try { await chatService.joinRoom(res.locals.room, req.user); res.status(200).json({ success: true, room: res.locals.room }); } catch (error) { this.log.error('failed to join chat room', { error }); return next(error); } } async getRoomMessages (req, res, next) { const { chat: chatService } = this.dtp.services; try { res.locals.pagination = this.getPaginationParameters(req, ChatController.MESSAGES_PER_PAGE); res.locals.messages = await chatService.getRoomMessages(res.locals.room, res.locals.pagination); res.render('chat/room/view'); } catch (error) { this.log.error('failed to present the chat room view', { error }); return next(error); } } async getRoomSettingsView (req, res) { res.render('chat/room/settings'); } async getRoomView (req, res, next) { const { chat: chatService } = this.dtp.services; try { res.locals.currentView = 'chat-room'; res.locals.pagination = this.getPaginationParameters(req, ChatController.MESSAGES_PER_PAGE); res.locals.messages = await chatService.getRoomMessages(res.locals.room, res.locals.pagination); res.render('chat/room/view'); } catch (error) { this.log.error('failed to present the chat room view', { error }); return next(error); } } async deleteChatMessage (req, res) { const { chat: chatService } = this.dtp.services; try { await chatService.removeMessage(res.locals.message); res.status(200).json({ success: true }); } catch (error) { this.log.error('failed to destroy chat room', { error }); res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } async deleteRoom (req, res) { const { chat: chatService } = this.dtp.services; try { await chatService.destroyRoom(req.user, res.locals.room); const displayList = this.createDisplayList('chat-room-delete'); displayList.navigateto('/'); res.status(200).json({ success: true, displayList }); } catch (error) { this.log.error('failed to destroy chat room', { error }); res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } }