move module functions to top level

GitOrigin-RevId: 73524dcec49faa05d1fd4dd4ffa6950803175f33
This commit is contained in:
Tim Alby 2022-01-07 12:48:39 +01:00 committed by Copybot
parent b5d2122f05
commit f3a5e8a0e8
4 changed files with 450 additions and 455 deletions

View file

@ -1,65 +1,66 @@
let MessageFormatter
module.exports = MessageFormatter = {
formatMessageForClientSide(message) {
if (message._id) {
message.id = message._id.toString()
delete message._id
}
const formattedMessage = {
id: message.id,
content: message.content,
timestamp: message.timestamp,
user_id: message.user_id,
}
if (message.edited_at) {
formattedMessage.edited_at = message.edited_at
}
return formattedMessage
},
formatMessagesForClientSide(messages) {
return messages.map(message => this.formatMessageForClientSide(message))
},
groupMessagesByThreads(rooms, messages) {
let room, thread
const roomsById = {}
for (room of rooms) {
roomsById[room._id.toString()] = room
}
const threads = {}
const getThread = function (room) {
const threadId = room.thread_id.toString()
if (threads[threadId]) {
return threads[threadId]
} else {
const thread = { messages: [] }
if (room.resolved) {
thread.resolved = true
thread.resolved_at = room.resolved.ts
thread.resolved_by_user_id = room.resolved.user_id
}
threads[threadId] = thread
return thread
}
}
for (const message of messages) {
room = roomsById[message.room_id.toString()]
if (room) {
thread = getThread(room)
thread.messages.push(
MessageFormatter.formatMessageForClientSide(message)
)
}
}
for (const threadId in threads) {
thread = threads[threadId]
thread.messages.sort((a, b) => a.timestamp - b.timestamp)
}
return threads
},
function formatMessageForClientSide(message) {
if (message._id) {
message.id = message._id.toString()
delete message._id
}
const formattedMessage = {
id: message.id,
content: message.content,
timestamp: message.timestamp,
user_id: message.user_id,
}
if (message.edited_at) {
formattedMessage.edited_at = message.edited_at
}
return formattedMessage
}
function formatMessagesForClientSide(messages) {
return messages.map(message => formatMessageForClientSide(message))
}
function groupMessagesByThreads(rooms, messages) {
let room, thread
const roomsById = {}
for (room of rooms) {
roomsById[room._id.toString()] = room
}
const threads = {}
const getThread = function (room) {
const threadId = room.thread_id.toString()
if (threads[threadId]) {
return threads[threadId]
} else {
const thread = { messages: [] }
if (room.resolved) {
thread.resolved = true
thread.resolved_at = room.resolved.ts
thread.resolved_by_user_id = room.resolved.user_id
}
threads[threadId] = thread
return thread
}
}
for (const message of messages) {
room = roomsById[message.room_id.toString()]
if (room) {
thread = getThread(room)
thread.messages.push(formatMessageForClientSide(message))
}
}
for (const threadId in threads) {
thread = threads[threadId]
thread.messages.sort((a, b) => a.timestamp - b.timestamp)
}
return threads
}
module.exports = {
formatMessagesForClientSide,
formatMessageForClientSide,
groupMessagesByThreads,
}

View file

@ -1,236 +1,221 @@
let MessageHttpController
const logger = require('@overleaf/logger')
const MessageManager = require('./MessageManager')
const MessageFormatter = require('./MessageFormatter')
const ThreadManager = require('../Threads/ThreadManager')
const { ObjectId } = require('../../mongodb')
module.exports = MessageHttpController = {
DEFAULT_MESSAGE_LIMIT: 50,
MAX_MESSAGE_LENGTH: 10 * 1024, // 10kb, about 1,500 words
const DEFAULT_MESSAGE_LIMIT = 50
const MAX_MESSAGE_LENGTH = 10 * 1024 // 10kb, about 1,500 words
getGlobalMessages(req, res, next) {
MessageHttpController._getMessages(
ThreadManager.GLOBAL_THREAD,
req,
res,
next
)
},
function getGlobalMessages(req, res, next) {
_getMessages(ThreadManager.GLOBAL_THREAD, req, res, next)
}
sendGlobalMessage(req, res, next) {
MessageHttpController._sendMessage(
ThreadManager.GLOBAL_THREAD,
req,
res,
next
)
},
function sendGlobalMessage(req, res, next) {
_sendMessage(ThreadManager.GLOBAL_THREAD, req, res, next)
}
sendThreadMessage(req, res, next) {
MessageHttpController._sendMessage(req.params.threadId, req, res, next)
},
function sendThreadMessage(req, res, next) {
_sendMessage(req.params.threadId, req, res, next)
}
getAllThreads(req, res, next) {
const { projectId } = req.params
logger.log({ projectId }, 'getting all threads')
ThreadManager.findAllThreadRooms(projectId, function (error, rooms) {
function getAllThreads(req, res, next) {
const { projectId } = req.params
logger.log({ projectId }, 'getting all threads')
ThreadManager.findAllThreadRooms(projectId, function (error, rooms) {
if (error) {
return next(error)
}
const roomIds = rooms.map(r => r._id)
MessageManager.findAllMessagesInRooms(roomIds, function (error, messages) {
if (error) {
return next(error)
}
const roomIds = rooms.map(r => r._id)
MessageManager.findAllMessagesInRooms(
roomIds,
function (error, messages) {
if (error) {
return next(error)
}
const threads = MessageFormatter.groupMessagesByThreads(
rooms,
messages
)
res.json(threads)
}
)
const threads = MessageFormatter.groupMessagesByThreads(rooms, messages)
res.json(threads)
})
},
})
}
resolveThread(req, res, next) {
const { projectId, threadId } = req.params
const { user_id: userId } = req.body
logger.log({ userId, projectId, threadId }, 'marking thread as resolved')
ThreadManager.resolveThread(projectId, threadId, userId, function (error) {
function resolveThread(req, res, next) {
const { projectId, threadId } = req.params
const { user_id: userId } = req.body
logger.log({ userId, projectId, threadId }, 'marking thread as resolved')
ThreadManager.resolveThread(projectId, threadId, userId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}
function reopenThread(req, res, next) {
const { projectId, threadId } = req.params
logger.log({ projectId, threadId }, 'reopening thread')
ThreadManager.reopenThread(projectId, threadId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}
function deleteThread(req, res, next) {
const { projectId, threadId } = req.params
logger.log({ projectId, threadId }, 'deleting thread')
ThreadManager.deleteThread(projectId, threadId, function (error, roomId) {
if (error) {
return next(error)
}
MessageManager.deleteAllMessagesInRoom(roomId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}, // No content
})
}
reopenThread(req, res, next) {
const { projectId, threadId } = req.params
logger.log({ projectId, threadId }, 'reopening thread')
ThreadManager.reopenThread(projectId, threadId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}, // No content
deleteThread(req, res, next) {
const { projectId, threadId } = req.params
logger.log({ projectId, threadId }, 'deleting thread')
ThreadManager.deleteThread(projectId, threadId, function (error, roomId) {
if (error) {
return next(error)
}
MessageManager.deleteAllMessagesInRoom(roomId, function (error) {
function editMessage(req, res, next) {
const { content } = req.body
const { projectId, threadId, messageId } = req.params
logger.log({ projectId, threadId, messageId, content }, 'editing message')
ThreadManager.findOrCreateThread(projectId, threadId, function (error, room) {
if (error) {
return next(error)
}
MessageManager.updateMessage(
room._id,
messageId,
content,
Date.now(),
function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
})
}, // No content
editMessage(req, res, next) {
const { content } = req.body
const { projectId, threadId, messageId } = req.params
logger.log({ projectId, threadId, messageId, content }, 'editing message')
ThreadManager.findOrCreateThread(
projectId,
threadId,
function (error, room) {
if (error) {
return next(error)
}
MessageManager.updateMessage(
room._id,
messageId,
content,
Date.now(),
function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
}
)
}
)
},
deleteMessage(req, res, next) {
const { projectId, threadId, messageId } = req.params
logger.log({ projectId, threadId, messageId }, 'deleting message')
ThreadManager.findOrCreateThread(
projectId,
threadId,
function (error, room) {
if (error) {
return next(error)
}
MessageManager.deleteMessage(
room._id,
messageId,
function (error, message) {
if (error) {
return next(error)
}
res.sendStatus(204)
}
)
}
)
},
_sendMessage(clientThreadId, req, res, next) {
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 > this.MAX_MESSAGE_LENGTH) {
return res
.status(400)
.send(`Content too long (> ${this.MAX_MESSAGE_LENGTH} bytes)`)
}
logger.log(
{ clientThreadId, projectId, userId, content },
'new message received'
)
ThreadManager.findOrCreateThread(
projectId,
clientThreadId,
function (error, thread) {
if (error) {
return next(error)
}
MessageManager.createMessage(
thread._id,
userId,
content,
Date.now(),
function (error, message) {
if (error) {
return next(error)
}
message = MessageFormatter.formatMessageForClientSide(message)
message.room_id = projectId
res.status(201).send(message)
}
)
}
)
},
_getMessages(clientThreadId, req, res, next) {
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 = MessageHttpController.DEFAULT_MESSAGE_LIMIT
}
logger.log(
{ limit, before, projectId, clientThreadId },
'get message request received'
)
ThreadManager.findOrCreateThread(
projectId,
clientThreadId,
function (error, thread) {
if (error) {
return next(error)
}
const threadObjectId = thread._id
logger.log(
{ limit, before, projectId, clientThreadId, threadObjectId },
'found or created thread'
)
MessageManager.getMessages(
threadObjectId,
limit,
before,
function (error, messages) {
if (error) {
return next(error)
}
messages = MessageFormatter.formatMessagesForClientSide(messages)
logger.log({ projectId, messages }, 'got messages')
res.status(200).send(messages)
}
)
}
)
},
})
}
function deleteMessage(req, res, next) {
const { projectId, threadId, messageId } = req.params
logger.log({ projectId, threadId, messageId }, 'deleting message')
ThreadManager.findOrCreateThread(projectId, threadId, function (error, room) {
if (error) {
return next(error)
}
MessageManager.deleteMessage(
room._id,
messageId,
function (error, message) {
if (error) {
return next(error)
}
res.sendStatus(204)
}
)
})
}
function _sendMessage(clientThreadId, req, res, next) {
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.log(
{ clientThreadId, projectId, userId, content },
'new message received'
)
ThreadManager.findOrCreateThread(
projectId,
clientThreadId,
function (error, thread) {
if (error) {
return next(error)
}
MessageManager.createMessage(
thread._id,
userId,
content,
Date.now(),
function (error, message) {
if (error) {
return next(error)
}
message = MessageFormatter.formatMessageForClientSide(message)
message.room_id = projectId
res.status(201).send(message)
}
)
}
)
}
function _getMessages(clientThreadId, req, res, next) {
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.log(
{ limit, before, projectId, clientThreadId },
'get message request received'
)
ThreadManager.findOrCreateThread(
projectId,
clientThreadId,
function (error, thread) {
if (error) {
return next(error)
}
const threadObjectId = thread._id
logger.log(
{ limit, before, projectId, clientThreadId, threadObjectId },
'found or created thread'
)
MessageManager.getMessages(
threadObjectId,
limit,
before,
function (error, messages) {
if (error) {
return next(error)
}
messages = MessageFormatter.formatMessagesForClientSide(messages)
logger.log({ projectId, messages }, 'got messages')
res.status(200).send(messages)
}
)
}
)
}
module.exports = {
getGlobalMessages,
sendGlobalMessage,
sendThreadMessage,
getAllThreads,
resolveThread,
reopenThread,
deleteThread,
editMessage,
deleteMessage,
}

View file

@ -3,91 +3,94 @@ const { db, ObjectId } = require('../../mongodb')
const metrics = require('@overleaf/metrics')
const logger = require('@overleaf/logger')
function createMessage(roomId, userId, content, timestamp, callback) {
let newMessageOpts = {
content,
room_id: roomId,
user_id: userId,
timestamp,
}
newMessageOpts = _ensureIdsAreObjectIds(newMessageOpts)
db.messages.insertOne(newMessageOpts, function (error, confirmation) {
if (error) {
return callback(error)
}
newMessageOpts._id = confirmation.insertedId
callback(null, newMessageOpts)
})
}
function getMessages(roomId, limit, before, callback) {
let query = { room_id: roomId }
if (before) {
query.timestamp = { $lt: before }
}
query = _ensureIdsAreObjectIds(query)
db.messages.find(query).sort({ timestamp: -1 }).limit(limit).toArray(callback)
}
function findAllMessagesInRooms(roomIds, callback) {
db.messages
.find({
room_id: { $in: roomIds },
})
.toArray(callback)
}
function deleteAllMessagesInRoom(roomId, callback) {
db.messages.deleteMany(
{
room_id: roomId,
},
callback
)
}
function updateMessage(roomId, messageId, content, timestamp, callback) {
const query = _ensureIdsAreObjectIds({
_id: messageId,
room_id: roomId,
})
db.messages.updateOne(
query,
{
$set: {
content,
edited_at: timestamp,
},
},
callback
)
}
function deleteMessage(roomId, messageId, callback) {
const query = _ensureIdsAreObjectIds({
_id: messageId,
room_id: roomId,
})
db.messages.deleteOne(query, callback)
}
function _ensureIdsAreObjectIds(query) {
if (query.user_id && !(query.user_id instanceof ObjectId)) {
query.user_id = ObjectId(query.user_id)
}
if (query.room_id && !(query.room_id instanceof ObjectId)) {
query.room_id = ObjectId(query.room_id)
}
if (query._id && !(query._id instanceof ObjectId)) {
query._id = ObjectId(query._id)
}
return query
}
module.exports = MessageManager = {
createMessage(roomId, userId, content, timestamp, callback) {
let newMessageOpts = {
content,
room_id: roomId,
user_id: userId,
timestamp,
}
newMessageOpts = this._ensureIdsAreObjectIds(newMessageOpts)
db.messages.insertOne(newMessageOpts, function (error, confirmation) {
if (error) {
return callback(error)
}
newMessageOpts._id = confirmation.insertedId
callback(null, newMessageOpts)
})
},
getMessages(roomId, limit, before, callback) {
let query = { room_id: roomId }
if (before) {
query.timestamp = { $lt: before }
}
query = this._ensureIdsAreObjectIds(query)
db.messages
.find(query)
.sort({ timestamp: -1 })
.limit(limit)
.toArray(callback)
},
findAllMessagesInRooms(roomIds, callback) {
db.messages
.find({
room_id: { $in: roomIds },
})
.toArray(callback)
},
deleteAllMessagesInRoom(roomId, callback) {
db.messages.deleteMany(
{
room_id: roomId,
},
callback
)
},
updateMessage(roomId, messageId, content, timestamp, callback) {
const query = this._ensureIdsAreObjectIds({
_id: messageId,
room_id: roomId,
})
db.messages.updateOne(
query,
{
$set: {
content,
edited_at: timestamp,
},
},
callback
)
},
deleteMessage(roomId, messageId, callback) {
const query = this._ensureIdsAreObjectIds({
_id: messageId,
room_id: roomId,
})
db.messages.deleteOne(query, callback)
},
_ensureIdsAreObjectIds(query) {
if (query.user_id && !(query.user_id instanceof ObjectId)) {
query.user_id = ObjectId(query.user_id)
}
if (query.room_id && !(query.room_id instanceof ObjectId)) {
query.room_id = ObjectId(query.room_id)
}
if (query._id && !(query._id instanceof ObjectId)) {
query._id = ObjectId(query._id)
}
return query
},
createMessage,
getMessages,
findAllMessagesInRooms,
deleteAllMessagesInRoom,
updateMessage,
deleteMessage,
}
;[
'createMessage',

View file

@ -3,114 +3,120 @@ const { db, ObjectId } = require('../../mongodb')
const logger = require('@overleaf/logger')
const metrics = require('@overleaf/metrics')
module.exports = ThreadManager = {
GLOBAL_THREAD: 'GLOBAL',
const GLOBAL_THREAD = 'GLOBAL'
findOrCreateThread(projectId, threadId, callback) {
let query, update
projectId = ObjectId(projectId.toString())
if (threadId !== ThreadManager.GLOBAL_THREAD) {
threadId = ObjectId(threadId.toString())
function findOrCreateThread(projectId, threadId, callback) {
let query, update
projectId = ObjectId(projectId.toString())
if (threadId !== GLOBAL_THREAD) {
threadId = ObjectId(threadId.toString())
}
if (threadId === GLOBAL_THREAD) {
query = {
project_id: projectId,
thread_id: { $exists: false },
}
if (threadId === ThreadManager.GLOBAL_THREAD) {
query = {
project_id: projectId,
thread_id: { $exists: false },
}
update = {
project_id: projectId,
}
} else {
query = {
project_id: projectId,
thread_id: threadId,
}
update = {
project_id: projectId,
thread_id: threadId,
}
update = {
project_id: projectId,
}
} else {
query = {
project_id: projectId,
thread_id: threadId,
}
update = {
project_id: projectId,
thread_id: threadId,
}
}
db.rooms.findOneAndUpdate(
query,
{ $set: update },
{ upsert: true, returnDocument: 'after' },
function (error, result) {
if (error) {
return callback(error)
}
callback(null, result.value)
}
)
},
findAllThreadRooms(projectId, callback) {
db.rooms
.find(
{
project_id: ObjectId(projectId.toString()),
thread_id: { $exists: true },
},
{
thread_id: 1,
resolved: 1,
}
)
.toArray(callback)
},
resolveThread(projectId, threadId, userId, callback) {
db.rooms.updateOne(
{
project_id: ObjectId(projectId.toString()),
thread_id: ObjectId(threadId.toString()),
},
{
$set: {
resolved: {
user_id: userId,
ts: new Date(),
},
},
},
callback
)
},
reopenThread(projectId, threadId, callback) {
db.rooms.updateOne(
{
project_id: ObjectId(projectId.toString()),
thread_id: ObjectId(threadId.toString()),
},
{
$unset: {
resolved: true,
},
},
callback
)
},
deleteThread(projectId, threadId, callback) {
this.findOrCreateThread(projectId, threadId, function (error, room) {
db.rooms.findOneAndUpdate(
query,
{ $set: update },
{ upsert: true, returnDocument: 'after' },
function (error, result) {
if (error) {
return callback(error)
}
db.rooms.deleteOne(
{
_id: room._id,
callback(null, result.value)
}
)
}
function findAllThreadRooms(projectId, callback) {
db.rooms
.find(
{
project_id: ObjectId(projectId.toString()),
thread_id: { $exists: true },
},
{
thread_id: 1,
resolved: 1,
}
)
.toArray(callback)
}
function resolveThread(projectId, threadId, userId, callback) {
db.rooms.updateOne(
{
project_id: ObjectId(projectId.toString()),
thread_id: ObjectId(threadId.toString()),
},
{
$set: {
resolved: {
user_id: userId,
ts: new Date(),
},
function (error) {
if (error) {
return callback(error)
}
callback(null, room._id)
},
},
callback
)
}
function reopenThread(projectId, threadId, callback) {
db.rooms.updateOne(
{
project_id: ObjectId(projectId.toString()),
thread_id: ObjectId(threadId.toString()),
},
{
$unset: {
resolved: true,
},
}
)
}
function deleteThread(projectId, threadId, callback) {
findOrCreateThread(projectId, threadId, function (error, room) {
if (error) {
return callback(error)
}
db.rooms.deleteOne(
{
_id: room._id,
},
function (error) {
if (error) {
return callback(error)
}
)
})
},
callback(null, room._id)
}
)
})
}
module.exports = ThreadManager = {
GLOBAL_THREAD,
findOrCreateThread,
findAllThreadRooms,
resolveThread,
reopenThread,
deleteThread,
}
;[
'findOrCreateThread',