Przeglądaj źródła

Add notifications web module (#28983)

* Add notifications web module

* implement getThreadMessage in chat

* Save comment mention notification

* check if recipient is a real user

* move commentMentionDelay

* use module-hooks types

* remove router

* updated collection name

GitOrigin-RevId: cf8240c88aac7d7e4de4bf51cfe2608b6b7e7918
Domagoj Kriskovic 10 miesięcy temu
rodzic
commit
71457d74cb

+ 28 - 0
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

+ 14 - 0
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

+ 8 - 0
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,

+ 1 - 0
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'),

+ 4 - 0
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

+ 14 - 0
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) => {

+ 10 - 0
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
+}