Merge pull request #10815 from overleaf/em-esm-chat

Move chat service to ES modules

GitOrigin-RevId: c08ae8328b8f3b539e6cfe052834b84bb3756330
This commit is contained in:
Eric Mc Sween 2022-12-13 07:37:49 -05:00 committed by Copybot
parent 7c1e91573c
commit 0a12c47b35
21 changed files with 198 additions and 295 deletions

View file

@ -13,7 +13,9 @@ const SHARELATEX_CONFIG = process.env.SHARELATEX_CONFIG
let settings
let settingsExist = false
const defaultsPath =
pathIfExists(Path.join(CWD, 'config/settings.defaults.cjs')) ||
pathIfExists(Path.join(CWD, 'config/settings.defaults.js')) ||
pathIfExists(Path.join(ENTRY_POINT_DIR, 'config/settings.defaults.cjs')) ||
pathIfExists(Path.join(ENTRY_POINT_DIR, 'config/settings.defaults.js'))
if (defaultsPath) {
console.log(`Using default settings from ${defaultsPath}`)
@ -25,6 +27,7 @@ if (defaultsPath) {
const overridesPath =
pathIfExists(SHARELATEX_CONFIG) ||
pathIfExists(Path.join(CWD, `config/settings.${NODE_ENV}.cjs`)) ||
pathIfExists(Path.join(CWD, `config/settings.${NODE_ENV}.js`))
if (overridesPath) {
console.log(`Using settings from ${overridesPath}`)

View file

@ -1,17 +1,14 @@
const logger = require('@overleaf/logger')
const settings = require('@overleaf/settings')
import logger from '@overleaf/logger'
import settings from '@overleaf/settings'
import { mongoClient } from './app/js/mongodb.js'
import { server } from './app/js/server.js'
const { mongoClient } = require('./app/js/mongodb')
const Server = require('./app/js/server')
if (!module.parent) {
// Called directly
const port = settings.internal.chat.port
const host = settings.internal.chat.host
mongoClient
const port = settings.internal.chat.port
const host = settings.internal.chat.host
mongoClient
.connect()
.then(() => {
Server.server.listen(port, host, function (err) {
server.listen(port, host, function (err) {
if (err) {
logger.fatal({ err }, `Cannot bind to ${host}:${port}. Exiting.`)
process.exit(1)
@ -23,6 +20,3 @@ if (!module.parent) {
logger.fatal({ err }, 'Cannot connect to mongo. Exiting.')
process.exit(1)
})
}
module.exports = Server.server

View file

@ -1,4 +1,4 @@
function formatMessageForClientSide(message) {
export function formatMessageForClientSide(message) {
if (message._id) {
message.id = message._id.toString()
delete message._id
@ -15,11 +15,11 @@ function formatMessageForClientSide(message) {
return formattedMessage
}
function formatMessagesForClientSide(messages) {
export function formatMessagesForClientSide(messages) {
return messages.map(message => formatMessageForClientSide(message))
}
function groupMessagesByThreads(rooms, messages) {
export function groupMessagesByThreads(rooms, messages) {
let room, thread
const roomsById = {}
for (room of rooms) {
@ -58,9 +58,3 @@ function groupMessagesByThreads(rooms, messages) {
return threads
}
module.exports = {
formatMessagesForClientSide,
formatMessageForClientSide,
groupMessagesByThreads,
}

View file

@ -1,26 +1,26 @@
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')
import logger from '@overleaf/logger'
import * as MessageManager from './MessageManager.js'
import * as MessageFormatter from './MessageFormatter.js'
import * as ThreadManager from '../Threads/ThreadManager.js'
import { ObjectId } from '../../mongodb.js'
import { expressify } from '../../util/promises.js'
const DEFAULT_MESSAGE_LIMIT = 50
const MAX_MESSAGE_LENGTH = 10 * 1024 // 10kb, about 1,500 words
async function getGlobalMessages(req, res) {
export const getGlobalMessages = expressify(async (req, res) => {
await _getMessages(ThreadManager.GLOBAL_THREAD, req, res)
}
})
async function sendGlobalMessage(req, res) {
export const sendGlobalMessage = expressify(async (req, res) => {
await _sendMessage(ThreadManager.GLOBAL_THREAD, req, res)
}
})
async function sendThreadMessage(req, res) {
export const sendThreadMessage = expressify(async (req, res) => {
await _sendMessage(req.params.threadId, req, res)
}
})
async function getAllThreads(req, res) {
export const getAllThreads = expressify(async (req, res) => {
const { projectId } = req.params
logger.debug({ projectId }, 'getting all threads')
const rooms = await ThreadManager.findAllThreadRooms(projectId)
@ -28,32 +28,32 @@ async function getAllThreads(req, res) {
const messages = await MessageManager.findAllMessagesInRooms(roomIds)
const threads = MessageFormatter.groupMessagesByThreads(rooms, messages)
res.json(threads)
}
})
async function resolveThread(req, res) {
export const resolveThread = expressify(async (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) {
export const reopenThread = expressify(async (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) {
export const deleteThread = expressify(async (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) {
export const editMessage = expressify(async (req, res) => {
const { content, userId } = req.body
const { projectId, threadId, messageId } = req.params
logger.debug({ projectId, threadId, messageId, content }, 'editing message')
@ -69,17 +69,17 @@ async function editMessage(req, res) {
return res.sendStatus(404)
}
res.sendStatus(204)
}
})
async function deleteMessage(req, res) {
export const deleteMessage = expressify(async (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) {
export const destroyProject = expressify(async (req, res) => {
const { projectId } = req.params
logger.debug({ projectId }, 'destroying project')
const rooms = await ThreadManager.findAllThreadRoomsAndGlobalThread(projectId)
@ -89,7 +89,7 @@ async function destroyProject(req, res) {
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
@ -155,16 +155,3 @@ async function _getMessages(clientThreadId, req, res) {
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),
}

View file

@ -1,9 +1,6 @@
let MessageManager
const { db, ObjectId } = require('../../mongodb')
const metrics = require('@overleaf/metrics')
const logger = require('@overleaf/logger')
import { db, ObjectId } from '../../mongodb.js'
async function createMessage(roomId, userId, content, timestamp) {
export async function createMessage(roomId, userId, content, timestamp) {
let newMessageOpts = {
content,
room_id: roomId,
@ -16,7 +13,7 @@ async function createMessage(roomId, userId, content, timestamp) {
return newMessageOpts
}
async function getMessages(roomId, limit, before) {
export async function getMessages(roomId, limit, before) {
let query = { room_id: roomId }
if (before) {
query.timestamp = { $lt: before }
@ -25,7 +22,7 @@ async function getMessages(roomId, limit, before) {
return db.messages.find(query).sort({ timestamp: -1 }).limit(limit).toArray()
}
async function findAllMessagesInRooms(roomIds) {
export async function findAllMessagesInRooms(roomIds) {
return db.messages
.find({
room_id: { $in: roomIds },
@ -33,19 +30,25 @@ async function findAllMessagesInRooms(roomIds) {
.toArray()
}
async function deleteAllMessagesInRoom(roomId) {
export async function deleteAllMessagesInRoom(roomId) {
await db.messages.deleteMany({
room_id: roomId,
})
}
async function deleteAllMessagesInRooms(roomIds) {
export async function deleteAllMessagesInRooms(roomIds) {
await db.messages.deleteMany({
room_id: { $in: roomIds },
})
}
async function updateMessage(roomId, messageId, userId, content, timestamp) {
export async function updateMessage(
roomId,
messageId,
userId,
content,
timestamp
) {
const query = _ensureIdsAreObjectIds({
_id: messageId,
room_id: roomId,
@ -62,7 +65,7 @@ async function updateMessage(roomId, messageId, userId, content, timestamp) {
return res.modifiedCount === 1
}
async function deleteMessage(roomId, messageId) {
export async function deleteMessage(roomId, messageId) {
const query = _ensureIdsAreObjectIds({
_id: messageId,
room_id: roomId,
@ -82,27 +85,3 @@ function _ensureIdsAreObjectIds(query) {
}
return query
}
module.exports = MessageManager = {
createMessage,
getMessages,
findAllMessagesInRooms,
deleteAllMessagesInRoom,
deleteAllMessagesInRooms,
updateMessage,
deleteMessage,
}
;[
'createMessage',
'getMessages',
'findAllMessagesInRooms',
'updateMessage',
'deleteMessage',
].map(method =>
metrics.timeAsyncMethod(
MessageManager,
method,
'mongo.MessageManager',
logger
)
)

View file

@ -1,11 +1,8 @@
let ThreadManager
const { db, ObjectId } = require('../../mongodb')
const logger = require('@overleaf/logger')
const metrics = require('@overleaf/metrics')
import { db, ObjectId } from '../../mongodb.js'
const GLOBAL_THREAD = 'GLOBAL'
export const GLOBAL_THREAD = 'GLOBAL'
async function findOrCreateThread(projectId, threadId) {
export async function findOrCreateThread(projectId, threadId) {
let query, update
projectId = ObjectId(projectId.toString())
if (threadId !== GLOBAL_THREAD) {
@ -39,7 +36,7 @@ async function findOrCreateThread(projectId, threadId) {
return result.value
}
async function findAllThreadRooms(projectId) {
export async function findAllThreadRooms(projectId) {
return db.rooms
.find(
{
@ -54,7 +51,7 @@ async function findAllThreadRooms(projectId) {
.toArray()
}
async function findAllThreadRoomsAndGlobalThread(projectId) {
export async function findAllThreadRoomsAndGlobalThread(projectId) {
return db.rooms
.find(
{
@ -68,7 +65,7 @@ async function findAllThreadRoomsAndGlobalThread(projectId) {
.toArray()
}
async function resolveThread(projectId, threadId, userId) {
export async function resolveThread(projectId, threadId, userId) {
await db.rooms.updateOne(
{
project_id: ObjectId(projectId.toString()),
@ -85,7 +82,7 @@ async function resolveThread(projectId, threadId, userId) {
)
}
async function reopenThread(projectId, threadId) {
export async function reopenThread(projectId, threadId) {
await db.rooms.updateOne(
{
project_id: ObjectId(projectId.toString()),
@ -99,7 +96,7 @@ async function reopenThread(projectId, threadId) {
)
}
async function deleteThread(projectId, threadId) {
export async function deleteThread(projectId, threadId) {
const room = await findOrCreateThread(projectId, threadId)
await db.rooms.deleteOne({
_id: room._id,
@ -107,28 +104,8 @@ async function deleteThread(projectId, threadId) {
return room._id
}
async function deleteAllThreadsInProject(projectId) {
export async function deleteAllThreadsInProject(projectId) {
await db.rooms.deleteMany({
project_id: ObjectId(projectId.toString()),
})
}
module.exports = ThreadManager = {
GLOBAL_THREAD,
findOrCreateThread,
findAllThreadRooms,
findAllThreadRoomsAndGlobalThread,
resolveThread,
reopenThread,
deleteThread,
deleteAllThreadsInProject,
}
;[
'findOrCreateThread',
'findAllThreadRooms',
'resolveThread',
'reopenThread',
'deleteThread',
].map(method =>
metrics.timeAsyncMethod(ThreadManager, method, 'mongo.ThreadManager', logger)
)

View file

@ -1,16 +1,12 @@
const Settings = require('@overleaf/settings')
const { MongoClient, ObjectId } = require('mongodb')
import Settings from '@overleaf/settings'
import { MongoClient } from 'mongodb'
const client = new MongoClient(Settings.mongo.url)
const db = client.db()
export { ObjectId } from 'mongodb'
const collections = {
messages: db.collection('messages'),
rooms: db.collection('rooms'),
}
module.exports = {
db: collections,
mongoClient: client,
ObjectId,
export const mongoClient = new MongoClient(Settings.mongo.url)
const mongoDb = mongoClient.db()
export const db = {
messages: mongoDb.collection('messages'),
rooms: mongoDb.collection('rooms'),
}

View file

@ -1,8 +1,7 @@
const MessageHttpController = require('./Features/Messages/MessageHttpController')
const { ObjectId } = require('./mongodb')
import * as MessageHttpController from './Features/Messages/MessageHttpController.js'
import { ObjectId } from './mongodb.js'
module.exports = {
route(app) {
export function route(app) {
app.param('projectId', function (req, res, next, projectId) {
if (ObjectId.isValid(projectId)) {
next()
@ -20,14 +19,8 @@ module.exports = {
})
// These are for backwards compatibility
app.get(
'/room/:projectId/messages',
MessageHttpController.getGlobalMessages
)
app.post(
'/room/:projectId/messages',
MessageHttpController.sendGlobalMessage
)
app.get('/room/:projectId/messages', MessageHttpController.getGlobalMessages)
app.post('/room/:projectId/messages', MessageHttpController.sendGlobalMessage)
app.get(
'/project/:projectId/messages',
@ -69,5 +62,4 @@ module.exports = {
app.delete('/project/:projectId', MessageHttpController.destroyProject)
app.get('/status', (req, res, next) => res.send('chat is alive'))
},
}

View file

@ -1,12 +1,15 @@
const metrics = require('@overleaf/metrics')
import http from 'http'
import metrics from '@overleaf/metrics'
import logger from '@overleaf/logger'
import express from 'express'
import bodyParser from 'body-parser'
import * as Router from './router.js'
metrics.initialize('chat')
const logger = require('@overleaf/logger')
logger.initialize('chat')
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const server = require('http').createServer(app)
const Router = require('./router')
export const app = express()
export const server = http.createServer(app)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
@ -14,8 +17,3 @@ app.use(metrics.http.monitor(logger))
metrics.injectMetricsRoute(app)
Router.route(app)
module.exports = {
server,
app,
}

View file

@ -3,10 +3,8 @@
*
* Any error will be passed to the error middlewares via `next()`
*/
function expressify(fn) {
export function expressify(fn) {
return (req, res, next) => {
fn(req, res, next).catch(next)
}
}
module.exports = { expressify }

View file

@ -3,6 +3,7 @@
"description": "The backend API that powers Overleaf chat",
"private": true,
"main": "app.js",
"type": "module",
"scripts": {
"start": "node $NODE_APP_OPTIONS app.js",
"test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP",

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
describe('Deleting a message', async function () {
const projectId = ObjectId().toString()

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
describe('Deleting a thread', async function () {
const projectId = ObjectId().toString()

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
const db = ChatApp.db

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
describe('Editing a message', async function () {
let projectId, userId, threadId

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
describe('Getting messages', async function () {
const userId1 = ObjectId().toString()

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
describe('Resolving a thread', async function () {
const projectId = ObjectId().toString()

View file

@ -1,8 +1,8 @@
const { ObjectId } = require('../../../app/js/mongodb')
const { expect } = require('chai')
import { ObjectId } from '../../../app/js/mongodb.js'
import { expect } from 'chai'
const ChatClient = require('./helpers/ChatClient')
const ChatApp = require('./helpers/ChatApp')
import * as ChatClient from './helpers/ChatClient.js'
import * as ChatApp from './helpers/ChatApp.js'
describe('Sending a message', async function () {
before(async function () {

View file

@ -1,9 +1,10 @@
const { db } = require('../../../../app/js/mongodb')
const app = require('../../../../app')
import { server } from '../../../../app/js/server.js'
export { db } from '../../../../app/js/mongodb.js'
let serverPromise = null
function startServer(resolve, reject) {
app.listen(3010, 'localhost', error => {
server.listen(3010, 'localhost', error => {
if (error) {
return reject(error)
}
@ -11,14 +12,9 @@ function startServer(resolve, reject) {
})
}
async function ensureRunning() {
export async function ensureRunning() {
if (!serverPromise) {
serverPromise = new Promise(startServer)
}
return serverPromise
}
module.exports = {
db,
ensureRunning,
}

View file

@ -1,4 +1,6 @@
const request = require('request').defaults({
import Request from 'request'
const request = Request.defaults({
baseUrl: 'http://localhost:3010',
})
@ -14,7 +16,7 @@ async function asyncRequest(options) {
})
}
async function sendGlobalMessage(projectId, userId, content) {
export async function sendGlobalMessage(projectId, userId, content) {
return asyncRequest({
method: 'post',
url: `/project/${projectId}/messages`,
@ -25,7 +27,7 @@ async function sendGlobalMessage(projectId, userId, content) {
})
}
async function getGlobalMessages(projectId) {
export async function getGlobalMessages(projectId) {
return asyncRequest({
method: 'get',
url: `/project/${projectId}/messages`,
@ -33,7 +35,7 @@ async function getGlobalMessages(projectId) {
})
}
async function sendMessage(projectId, threadId, userId, content) {
export async function sendMessage(projectId, threadId, userId, content) {
return asyncRequest({
method: 'post',
url: `/project/${projectId}/thread/${threadId}/messages`,
@ -44,7 +46,7 @@ async function sendMessage(projectId, threadId, userId, content) {
})
}
async function getThreads(projectId) {
export async function getThreads(projectId) {
return asyncRequest({
method: 'get',
url: `/project/${projectId}/threads`,
@ -52,7 +54,7 @@ async function getThreads(projectId) {
})
}
async function resolveThread(projectId, threadId, userId) {
export async function resolveThread(projectId, threadId, userId) {
return asyncRequest({
method: 'post',
url: `/project/${projectId}/thread/${threadId}/resolve`,
@ -62,21 +64,21 @@ async function resolveThread(projectId, threadId, userId) {
})
}
async function reopenThread(projectId, threadId) {
export async function reopenThread(projectId, threadId) {
return asyncRequest({
method: 'post',
url: `/project/${projectId}/thread/${threadId}/reopen`,
})
}
async function deleteThread(projectId, threadId) {
export async function deleteThread(projectId, threadId) {
return asyncRequest({
method: 'delete',
url: `/project/${projectId}/thread/${threadId}`,
})
}
async function editMessage(projectId, threadId, messageId, content) {
export async function editMessage(projectId, threadId, messageId, content) {
return asyncRequest({
method: 'post',
url: `/project/${projectId}/thread/${threadId}/messages/${messageId}/edit`,
@ -86,7 +88,7 @@ async function editMessage(projectId, threadId, messageId, content) {
})
}
async function editMessageWithUser(
export async function editMessageWithUser(
projectId,
threadId,
messageId,
@ -103,30 +105,16 @@ async function editMessageWithUser(
})
}
async function deleteMessage(projectId, threadId, messageId) {
export async function deleteMessage(projectId, threadId, messageId) {
return asyncRequest({
method: 'delete',
url: `/project/${projectId}/thread/${threadId}/messages/${messageId}`,
})
}
async function destroyProject(projectId) {
export async function destroyProject(projectId) {
return asyncRequest({
method: 'delete',
url: `/project/${projectId}`,
})
}
module.exports = {
sendGlobalMessage,
getGlobalMessages,
sendMessage,
getThreads,
resolveThread,
reopenThread,
deleteThread,
editMessage,
editMessageWithUser,
deleteMessage,
destroyProject,
}