const logger = require('@overleaf/logger') const MessageManager = require('./MessageManager') const MessageFormatter = require('./MessageFormatter') const ThreadManager = require('../Threads/ThreadManager') const { ObjectId } = require('../../mongodb') const { expressify } = require('../../util/promises') const DEFAULT_MESSAGE_LIMIT = 50 const MAX_MESSAGE_LENGTH = 10 * 1024 // 10kb, about 1,500 words async function getGlobalMessages(req, res) { await _getMessages(ThreadManager.GLOBAL_THREAD, req, res) } async function sendGlobalMessage(req, res) { await _sendMessage(ThreadManager.GLOBAL_THREAD, req, res) } async function sendThreadMessage(req, res) { await _sendMessage(req.params.threadId, req, res) } async function getAllThreads(req, res) { const { projectId } = req.params logger.debug({ projectId }, 'getting all threads') const rooms = await ThreadManager.findAllThreadRooms(projectId) const roomIds = rooms.map(r => r._id) const messages = await MessageManager.findAllMessagesInRooms(roomIds) const threads = MessageFormatter.groupMessagesByThreads(rooms, messages) res.json(threads) } async function resolveThread(req, res) { const { projectId, threadId } = req.params const { user_id: userId } = req.body logger.debug({ userId, projectId, threadId }, 'marking thread as resolved') await ThreadManager.resolveThread(projectId, threadId, userId) res.sendStatus(204) } async function reopenThread(req, res) { const { projectId, threadId } = req.params logger.debug({ projectId, threadId }, 'reopening thread') await ThreadManager.reopenThread(projectId, threadId) res.sendStatus(204) } async function deleteThread(req, res) { const { projectId, threadId } = req.params logger.debug({ projectId, threadId }, 'deleting thread') const roomId = await ThreadManager.deleteThread(projectId, threadId) await MessageManager.deleteAllMessagesInRoom(roomId) res.sendStatus(204) } async function editMessage(req, res) { const { content, userId } = req.body const { projectId, threadId, messageId } = req.params logger.debug({ projectId, threadId, messageId, content }, 'editing message') const room = await ThreadManager.findOrCreateThread(projectId, threadId) const found = await MessageManager.updateMessage( room._id, messageId, userId, content, Date.now() ) if (!found) { return res.sendStatus(404) } res.sendStatus(204) } async function deleteMessage(req, res) { const { projectId, threadId, messageId } = req.params logger.debug({ projectId, threadId, messageId }, 'deleting message') const room = await ThreadManager.findOrCreateThread(projectId, threadId) await MessageManager.deleteMessage(room._id, messageId) res.sendStatus(204) } async function destroyProject(req, res) { const { projectId } = req.params logger.debug({ projectId }, 'destroying project') const rooms = await ThreadManager.findAllThreadRoomsAndGlobalThread(projectId) const roomIds = rooms.map(r => r._id) logger.debug({ projectId, roomIds }, 'deleting all messages in rooms') await MessageManager.deleteAllMessagesInRooms(roomIds) logger.debug({ projectId }, 'deleting all threads in project') await ThreadManager.deleteAllThreadsInProject(projectId) res.sendStatus(204) } async function _sendMessage(clientThreadId, req, res) { const { user_id: userId, content } = req.body const { projectId } = req.params if (!ObjectId.isValid(userId)) { return res.status(400).send('Invalid userId') } if (!content) { return res.status(400).send('No content provided') } if (content.length > MAX_MESSAGE_LENGTH) { return res .status(400) .send(`Content too long (> ${MAX_MESSAGE_LENGTH} bytes)`) } logger.debug( { clientThreadId, projectId, userId, content }, 'new message received' ) const thread = await ThreadManager.findOrCreateThread( projectId, clientThreadId ) let message = await MessageManager.createMessage( thread._id, userId, content, Date.now() ) message = MessageFormatter.formatMessageForClientSide(message) message.room_id = projectId res.status(201).send(message) } async function _getMessages(clientThreadId, req, res) { let before, limit const { projectId } = req.params if (req.query.before) { before = parseInt(req.query.before, 10) } else { before = null } if (req.query.limit) { limit = parseInt(req.query.limit, 10) } else { limit = DEFAULT_MESSAGE_LIMIT } logger.debug( { limit, before, projectId, clientThreadId }, 'get message request received' ) const thread = await ThreadManager.findOrCreateThread( projectId, clientThreadId ) const threadObjectId = thread._id logger.debug( { limit, before, projectId, clientThreadId, threadObjectId }, 'found or created thread' ) let messages = await MessageManager.getMessages(threadObjectId, limit, before) messages = MessageFormatter.formatMessagesForClientSide(messages) logger.debug({ projectId, messages }, 'got messages') res.status(200).send(messages) } module.exports = { getGlobalMessages: expressify(getGlobalMessages), sendGlobalMessage: expressify(sendGlobalMessage), sendThreadMessage: expressify(sendThreadMessage), getAllThreads: expressify(getAllThreads), resolveThread: expressify(resolveThread), reopenThread: expressify(reopenThread), deleteThread: expressify(deleteThread), editMessage: expressify(editMessage), deleteMessage: expressify(deleteMessage), destroyProject: expressify(destroyProject), }