diff --git a/services/chat/app/js/Features/Messages/MessageHttpController.js b/services/chat/app/js/Features/Messages/MessageHttpController.js index 1a14ae66a0..4dd36d662b 100644 --- a/services/chat/app/js/Features/Messages/MessageHttpController.js +++ b/services/chat/app/js/Features/Messages/MessageHttpController.js @@ -62,6 +62,10 @@ export async function getThread(context) { return await callMessageHttpController(context, _getThread) } +export async function getThreadMessage(context) { + return await callMessageHttpController(context, _getThreadMessage) +} + export async function resolveThread(context) { return await callMessageHttpController(context, _resolveThread) } @@ -208,6 +212,30 @@ const _getThread = async (req, res) => { } } +const _getThreadMessage = async (req, res) => { + const { projectId, threadId, messageId } = req.params + logger.debug( + { projectId, threadId, messageId }, + 'getting single thread message' + ) + try { + const room = await ThreadManager.findThread(projectId, threadId) + const message = await MessageManager.getMessage(room._id, messageId) + const formattedMsg = MessageFormatter.formatMessageForClientSide(message) + + res.status(200).setBody(formattedMsg) + } catch (error) { + if ( + error instanceof ThreadManager.MissingThreadError || + error instanceof MessageManager.MissingMessageError + ) { + res.status(404) + return + } + throw error + } +} + const _resolveThread = async (req, res) => { const { projectId, threadId } = req.params const { user_id: userId } = req.body diff --git a/services/chat/chat.yaml b/services/chat/chat.yaml index 8e13031ff3..1438706749 100644 --- a/services/chat/chat.yaml +++ b/services/chat/chat.yaml @@ -243,6 +243,20 @@ paths: name: messageId in: path required: true + get: + summary: Get thread message + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + '404': + description: Message not found + operationId: getThreadMessage + description: Get a specific message by message ID from the thread with Thread ID and Project ID provided delete: summary: Delete message operationId: deleteMessage diff --git a/services/web/app/src/Features/Chat/ChatApiHandler.js b/services/web/app/src/Features/Chat/ChatApiHandler.js index c3be2746bc..5f7dfbe3db 100644 --- a/services/web/app/src/Features/Chat/ChatApiHandler.js +++ b/services/web/app/src/Features/Chat/ChatApiHandler.js @@ -8,6 +8,12 @@ async function getThread(projectId, threadId) { return await fetchJson(chatApiUrl(`/project/${projectId}/thread/${threadId}`)) } +async function getThreadMessage(projectId, threadId, messageId) { + return await fetchJson( + chatApiUrl(`/project/${projectId}/thread/${threadId}/messages/${messageId}`) + ) +} + async function getThreads(projectId) { return await fetchJson(chatApiUrl(`/project/${projectId}/threads`)) } @@ -161,6 +167,7 @@ function chatApiUrl(path) { module.exports = { getThread: callbackify(getThread), + getThreadMessage: callbackify(getThreadMessage), getThreads: callbackify(getThreads), destroyProject: callbackify(destroyProject), sendGlobalMessage: callbackify(sendGlobalMessage), @@ -180,6 +187,7 @@ module.exports = { generateThreadData: callbackify(generateThreadData), promises: { getThread, + getThreadMessage, getThreads, destroyProject, sendGlobalMessage, diff --git a/services/web/app/src/infrastructure/mongodb.js b/services/web/app/src/infrastructure/mongodb.js index ffa5e00989..737bcf023d 100644 --- a/services/web/app/src/infrastructure/mongodb.js +++ b/services/web/app/src/infrastructure/mongodb.js @@ -54,6 +54,7 @@ const db = { messages: internalDb.collection('messages'), migrations: internalDb.collection('migrations'), notifications: internalDb.collection('notifications'), + emailNotifications: internalDb.collection('emailNotifications'), oauthAccessTokens: internalDb.collection('oauthAccessTokens'), oauthApplications: internalDb.collection('oauthApplications'), oauthAuthorizationCodes: internalDb.collection('oauthAuthorizationCodes'), diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index b90a098c98..93290465f5 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -715,6 +715,10 @@ module.exports = { parseInt(process.env.OVERLEAF_PROJECT_HARD_DELETION_DELAY, 10) || 1000 * 60 * 60 * 24 * 90, // 90 days + // Delay before sending comment mention notifications + commentMentionDelay: + parseInt(process.env.COMMENT_MENTION_DELAY_MINUTES) || 30 * 60 * 1000, // 30 minutes + // Maximum JSON size in HTTP requests // We should be able to process twice the max doc length, to allow for // - the doc content diff --git a/services/web/test/acceptance/src/mocks/MockChatApi.mjs b/services/web/test/acceptance/src/mocks/MockChatApi.mjs index e3fe94d631..687cb919b4 100644 --- a/services/web/test/acceptance/src/mocks/MockChatApi.mjs +++ b/services/web/test/acceptance/src/mocks/MockChatApi.mjs @@ -58,6 +58,20 @@ class MockChatApi extends AbstractMockApi { res.json(this.getThread(req.params.project_id, req.params.thread_id)) } ) + this.app.get( + '/project/:project_id/thread/:thread_id/messages/:message_id', + (req, res) => { + const projectId = req.params.project_id + const threadId = req.params.thread_id + const messageId = req.params.message_id + const thread = this.getThread(projectId, threadId) + const message = thread.find(msg => msg.id === messageId) + if (!message) { + return res.status(404).json({ error: 'Message not found' }) + } + res.json(message) + } + ) this.app.post( '/project/:project_id/thread/:thread_id/messages', (req, res) => { diff --git a/services/web/types/module-hooks.ts b/services/web/types/module-hooks.ts new file mode 100644 index 0000000000..b676017f03 --- /dev/null +++ b/services/web/types/module-hooks.ts @@ -0,0 +1,10 @@ +/** + * Types for module hook events fired across the application + */ + +export type CommentAddedEvent = { + projectId: string + userId: string + threadId: string + messageId: string +}