From ff7fc86b3ca1ed5471dc0669082b0c0949fd1bcc Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 3 Jun 2024 00:49:08 -0300 Subject: [PATCH 01/38] refactor: adds listener calls on Subscription related features --- .../meteor/app/api/server/v1/subscriptions.ts | 1 + .../server/methods/saveSettings.ts | 11 +- .../server/functions/saveRoomCustomFields.ts | 10 +- .../server/functions/saveRoomEncrypted.ts | 4 +- .../server/functions/saveRoomName.ts | 18 +- .../server/functions/saveRoomType.ts | 9 +- .../functions/handleSuggestedGroupKey.ts | 13 +- .../app/e2e/server/methods/updateGroupKey.ts | 6 +- .../federation/server/endpoints/dispatch.js | 12 +- .../server/classes/ImportDataConverter.ts | 8 +- .../functions/addUserToDefaultChannels.ts | 8 +- .../app/lib/server/functions/addUserToRoom.ts | 8 +- .../app/lib/server/functions/archiveRoom.ts | 6 +- .../lib/server/functions/cleanRoomHistory.ts | 15 +- .../app/lib/server/functions/createRoom.ts | 16 +- .../app/lib/server/functions/deleteRoom.ts | 12 +- .../app/lib/server/functions/deleteUser.ts | 6 +- .../functions/relinquishRoomOwnerships.ts | 16 +- .../server/functions/removeUserFromRoom.ts | 8 +- .../saveCustomFieldsWithoutValidation.ts | 4 +- .../lib/server/functions/saveUserIdentity.ts | 28 +- .../server/functions/setUserActiveStatus.ts | 16 +- .../app/lib/server/functions/unarchiveRoom.ts | 6 +- .../server/functions/updateGroupDMsName.ts | 5 +- .../app/lib/server/lib/notifyListener.ts | 292 ++++++++++++++++++ .../lib/server/lib/notifyUsersOnMessage.ts | 94 ++++-- .../app/lib/server/methods/blockUser.ts | 17 +- .../app/lib/server/methods/unblockUser.ts | 18 +- .../app/livechat/server/lib/Contacts.ts | 12 +- apps/meteor/app/livechat/server/lib/Helper.ts | 14 +- .../app/livechat/server/lib/LivechatTyped.ts | 21 +- .../server/unreadMessages.ts | 12 +- .../methods/saveNotificationSettings.ts | 8 +- apps/meteor/app/threads/server/functions.ts | 76 +++-- .../server/hooks/onCloseLivechat.ts | 7 +- .../services/omnichannel.internalService.ts | 29 +- .../server/hooks/afterSaveMessage.ts | 4 +- .../server/database/watchCollections.ts | 2 +- apps/meteor/server/lib/readMessages.ts | 4 +- apps/meteor/server/lib/resetUserE2EKey.ts | 9 +- .../meteor/server/methods/addAllUserToRoom.ts | 6 +- apps/meteor/server/methods/addRoomLeader.ts | 3 +- .../meteor/server/methods/addRoomModerator.ts | 4 +- apps/meteor/server/methods/addRoomOwner.ts | 3 +- apps/meteor/server/methods/hideRoom.ts | 10 +- apps/meteor/server/methods/ignoreUser.ts | 17 +- apps/meteor/server/methods/openRoom.ts | 10 +- .../meteor/server/methods/removeRoomLeader.ts | 4 +- .../server/methods/removeRoomModerator.ts | 4 +- apps/meteor/server/methods/removeRoomOwner.ts | 3 +- .../server/methods/removeUserFromRoom.ts | 8 +- .../server/methods/saveUserPreferences.ts | 92 +++--- apps/meteor/server/methods/toggleFavorite.ts | 10 +- apps/meteor/server/models/raw/Roles.ts | 27 +- .../meteor/server/models/raw/Subscriptions.ts | 127 ++++++-- .../rocket-chat/adapters/Room.ts | 86 ++++-- .../src/models/ISubscriptionsModel.ts | 52 +++- 57 files changed, 1048 insertions(+), 283 deletions(-) diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index 9d81fe6bef65..b92d9ba572fd 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -82,6 +82,7 @@ API.v1.addRoute( async post() { const { readThreads = false } = this.bodyParams; const roomId = 'rid' in this.bodyParams ? this.bodyParams.rid : this.bodyParams.roomId; + await readMessages(roomId, this.userId, readThreads); return API.v1.success(); diff --git a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts index e396d78887a9..a68eaa8007d9 100644 --- a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts +++ b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts @@ -4,6 +4,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -53,13 +54,17 @@ Meteor.methods({ }); } - await Subscriptions.updateAutoTranslateById(subscription._id, value === '1'); + (await Subscriptions.updateAutoTranslateById(subscription._id, value === '1')).modifiedCount && + void notifyOnSubscriptionChangedById(subscription._id); + if (!subscription.autoTranslateLanguage && options.defaultLanguage) { - await Subscriptions.updateAutoTranslateLanguageById(subscription._id, options.defaultLanguage); + (await Subscriptions.updateAutoTranslateLanguageById(subscription._id, options.defaultLanguage)).modifiedCount && + void notifyOnSubscriptionChangedById(subscription._id); } break; case 'autoTranslateLanguage': - await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value); + (await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value)).modifiedCount && + void notifyOnSubscriptionChangedById(subscription._id); break; } diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts index 55d40cf3d7e6..94fbb0100537 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts @@ -3,21 +3,25 @@ import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; + export const saveRoomCustomFields = async function (rid: string, roomCustomFields: Record): Promise { if (!Match.test(rid, String)) { throw new Meteor.Error('invalid-room', 'Invalid room', { function: 'RocketChat.saveRoomCustomFields', }); } + if (!Match.test(roomCustomFields, Object)) { throw new Meteor.Error('invalid-roomCustomFields-type', 'Invalid roomCustomFields type', { function: 'RocketChat.saveRoomCustomFields', }); } - const ret = await Rooms.setCustomFieldsById(rid, roomCustomFields); + + const response = await Rooms.setCustomFieldsById(rid, roomCustomFields); // Update customFields of any user's Subscription related with this rid - await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields); + (await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); - return ret; + return response; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts index ed07540ba2b0..24321391cc27 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts @@ -6,6 +6,8 @@ import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; + export const saveRoomEncrypted = async function (rid: string, encrypted: boolean, user: IUser, sendMessage = true): Promise { if (!Match.test(rid, String)) { throw new Meteor.Error('invalid-room', 'Invalid room', { @@ -27,7 +29,7 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean } if (encrypted) { - await Subscriptions.disableAutoTranslateByRoomId(rid); + (await Subscriptions.disableAutoTranslateByRoomId(rid)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); } return update; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts index 0fc15f878bcf..34f8f8690070 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts @@ -8,11 +8,17 @@ import type { Document, UpdateResult } from 'mongodb'; import { callbacks } from '../../../../lib/callbacks'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability'; -import { notifyOnIntegrationChangedByChannels } from '../../../lib/server/lib/notifyListener'; +import { notifyOnIntegrationChangedByChannels, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; const updateFName = async (rid: string, displayName: string): Promise<(UpdateResult | Document)[]> => { - return Promise.all([Rooms.setFnameById(rid, displayName), Subscriptions.updateFnameByRoomId(rid, displayName)]); + const responses = await Promise.all([Rooms.setFnameById(rid, displayName), Subscriptions.updateFnameByRoomId(rid, displayName)]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + + return responses; }; const updateRoomName = async (rid: string, displayName: string, slugifiedRoomName: string) => { @@ -24,10 +30,16 @@ const updateRoomName = async (rid: string, displayName: string, slugifiedRoomNam }); } - return Promise.all([ + const responses = await Promise.all([ Rooms.setNameById(rid, slugifiedRoomName, displayName), Subscriptions.updateNameAndAlertByRoomId(rid, slugifiedRoomName, displayName), ]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + + return responses; }; export async function saveRoomName( diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts index e8a60d1ea0eb..4600d1d46a80 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts @@ -8,6 +8,7 @@ import type { UpdateResult, Document } from 'mongodb'; import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig'; import { i18n } from '../../../../server/lib/i18n'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; export const saveRoomType = async function ( @@ -41,11 +42,16 @@ export const saveRoomType = async function ( }); } - const result = (await Rooms.setTypeById(rid, roomType)) && (await Subscriptions.updateTypeByRoomId(rid, roomType)); + const result = await Promise.all([Rooms.setTypeById(rid, roomType), Subscriptions.updateTypeByRoomId(rid, roomType)]); + if (!result) { return result; } + if (result[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + if (sendMessage) { let message; if (roomType === 'c') { @@ -59,5 +65,6 @@ export const saveRoomType = async function ( } await Message.saveSystemMessage('room_changed_privacy', rid, message, user); } + return result; }; diff --git a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts index dcd1f82edbc8..0d1e55a2ad09 100644 --- a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts +++ b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts @@ -1,6 +1,8 @@ import { Subscriptions } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; + export async function handleSuggestedGroupKey( handle: 'accept' | 'reject', rid: string, @@ -21,9 +23,12 @@ export async function handleSuggestedGroupKey( throw new Meteor.Error('error-no-suggested-key-available', 'No suggested key available', { method }); } - if (handle === 'accept') { - await Subscriptions.setGroupE2EKey(sub._id, suggestedKey); - } + const e2eKeyUpdateResult = + handle === 'accept' + ? await Subscriptions.setGroupE2EKey(sub._id, suggestedKey) + : await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); - await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); + if (e2eKeyUpdateResult?.modifiedCount) { + void notifyOnSubscriptionChangedById(sub._id); + } } diff --git a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts index 30053cc7164a..5a11babe8bf2 100644 --- a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts +++ b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts @@ -2,6 +2,8 @@ import { Subscriptions } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -22,14 +24,14 @@ Meteor.methods({ if (mySub) { // Setting the key to myself, can set directly to the final field if (userId === uid) { - await Subscriptions.setGroupE2EKey(mySub._id, key); + (await Subscriptions.setGroupE2EKey(mySub._id, key)).modifiedCount && void notifyOnSubscriptionChangedById(mySub._id); return; } // uid also has subscription to this room const userSub = await Subscriptions.findOneByRoomIdAndUserId(rid, uid); if (userSub) { - await Subscriptions.setGroupE2ESuggestedKey(userSub._id, key); + (await Subscriptions.setGroupE2ESuggestedKey(userSub._id, key)).modifiedCount && void notifyOnSubscriptionChangedById(userSub._id); } } }, diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index 4cab0b0c41e8..150849820e88 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -177,7 +177,11 @@ const eventHandlers = { } = event; // Remove the user's subscription - await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + + if (deletedSubscription) { + void notifyOnSubscriptionChangedByUserAndRoomId(user._id, roomId, 'removed'); + } // Refresh the servers list await FederationServers.refreshServers(); @@ -205,7 +209,11 @@ const eventHandlers = { } = event; // Remove the user's subscription - await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + + if (deletedSubscription) { + void notifyOnSubscriptionChangedByUserAndRoomId(user._id, roomId, 'removed'); + } // Refresh the servers list await FederationServers.refreshServers(); diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts index 493d14061bf2..1913e07e70e2 100644 --- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts @@ -28,6 +28,7 @@ import { generateUsernameSuggestion } from '../../../lib/server/functions/getUse import { insertMessage } from '../../../lib/server/functions/insertMessage'; import { saveUserIdentity } from '../../../lib/server/functions/saveUserIdentity'; import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import { createChannelMethod } from '../../../lib/server/methods/createChannel'; import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup'; import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; @@ -1155,8 +1156,11 @@ export class ImportDataConverter { } async archiveRoomById(rid: string) { - await Rooms.archiveById(rid); - await Subscriptions.archiveByRoomId(rid); + const responses = await Promise.all([Rooms.archiveById(rid), Subscriptions.archiveByRoomId(rid)]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } } async convertData(startedByUserId: string, callbacks: IConversionCallbacks = {}): Promise { diff --git a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts index b6d977dc36e2..3fb9c419aa5f 100644 --- a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts +++ b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts @@ -5,6 +5,7 @@ import { Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; +import { notifyOnSubscriptionChangedById } from '../lib/notifyListener'; import { getDefaultChannels } from './getDefaultChannels'; export const addUserToDefaultChannels = async function (user: IUser, silenced?: boolean): Promise { @@ -14,8 +15,9 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?: for await (const room of defaultRooms) { if (!(await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 } }))) { const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user); + // Add a subscription to this user - await Subscriptions.createWithRoomAndUser(room, user, { + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, { ts: new Date(), open: true, alert: true, @@ -27,6 +29,10 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?: ...getDefaultSubscriptionPref(user), }); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } + // Insert user joined message if (!silenced) { await Message.saveSystemMessage('uj', room._id, user.username || '', user); diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts index e377ba3c4600..cbf69ab8408e 100644 --- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts +++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts @@ -10,7 +10,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener'; export const addUserToRoom = async function ( rid: string, @@ -75,7 +75,7 @@ export const addUserToRoom = async function ( const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(userToBeAdded); - await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, { + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, { ts: now, open: true, alert: true, @@ -86,6 +86,10 @@ export const addUserToRoom = async function ( ...getDefaultSubscriptionPref(userToBeAdded as IUser), }); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } + void notifyOnRoomChangedById(rid); if (!userToBeAdded.username) { diff --git a/apps/meteor/app/lib/server/functions/archiveRoom.ts b/apps/meteor/app/lib/server/functions/archiveRoom.ts index 3378d69f99ff..d643123fbd06 100644 --- a/apps/meteor/app/lib/server/functions/archiveRoom.ts +++ b/apps/meteor/app/lib/server/functions/archiveRoom.ts @@ -3,11 +3,13 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; -import { notifyOnRoomChanged } from '../lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; export const archiveRoom = async function (rid: string, user: IMessage['u']): Promise { await Rooms.archiveById(rid); - await Subscriptions.archiveByRoomId(rid); + + (await Subscriptions.archiveByRoomId(rid)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); + await Message.saveSystemMessage('room-archived', rid, '', user); const room = await Rooms.findOneById(rid); diff --git a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts index f53061995152..1e1a7f0936be 100644 --- a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts +++ b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts @@ -4,7 +4,7 @@ import { Messages, Rooms, Subscriptions, ReadReceipts, Users } from '@rocket.cha import { i18n } from '../../../../server/lib/i18n'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener'; import { deleteRoom } from './deleteRoom'; export async function cleanRoomHistory({ @@ -75,6 +75,7 @@ export async function cleanRoomHistory({ if (!ignoreThreads) { const threads = new Set(); + await Messages.findThreadsByRoomIdPinnedTimestampAndUsers( { rid, pinned: excludePinned, ignoreDiscussion, ts, users: fromUsers }, { projection: { _id: 1 } }, @@ -83,7 +84,17 @@ export async function cleanRoomHistory({ }); if (threads.size > 0) { - await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); + const subscriptionIds = (await Subscriptions.findUnreadThreadsByRoomId(rid, [...threads], { projection: { _id: 1 } })) + .toArray() + .map(({ _id }) => _id); + + const removedUnreadThreadsResponse = await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); + + if (removedUnreadThreadsResponse.modifiedCount) { + for await (const id of subscriptionIds) { + await notifyOnSubscriptionChangedById(id); + } + } } } diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 19e5fb2f9489..885454e667ac 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -12,7 +12,7 @@ import { beforeCreateRoomCallback } from '../../../../lib/callbacks/beforeCreate import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; -import { notifyOnRoomChanged } from '../lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChangedById } from '../lib/notifyListener'; import { createDirectRoom } from './createDirectRoom'; const isValidName = (name: unknown): name is string => { @@ -47,7 +47,11 @@ async function createUsersSubscriptions({ ...getDefaultSubscriptionPref(owner), }; - await Subscriptions.createWithRoomAndUser(room, owner, extra); + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, owner, extra); + + if (insertedId) { + await notifyOnRoomChanged(room, 'inserted'); + } return; } @@ -98,7 +102,13 @@ async function createUsersSubscriptions({ await Users.addRoomByUserIds(memberIds, room._id); } - await Subscriptions.createWithRoomAndManyUsers(room, subs); + const { insertedIds } = await Subscriptions.createWithRoomAndManyUsers(room, subs); + + if (Object.keys(insertedIds).length) { + for await (const insertedId of Object.values(insertedIds)) { + await notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } + } await Rooms.incUsersCountById(room._id, subs.length); } diff --git a/apps/meteor/app/lib/server/functions/deleteRoom.ts b/apps/meteor/app/lib/server/functions/deleteRoom.ts index a605d82b08c2..2ea8bd38c320 100644 --- a/apps/meteor/app/lib/server/functions/deleteRoom.ts +++ b/apps/meteor/app/lib/server/functions/deleteRoom.ts @@ -2,16 +2,20 @@ import { Messages, Rooms, Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; export const deleteRoom = async function (rid: string): Promise { await FileUpload.removeFilesByRoomId(rid); + await Messages.removeByRoomId(rid); + await callbacks.run('beforeDeleteRoom', rid); - await Subscriptions.removeByRoomId(rid); + + (await Subscriptions.removeByRoomId(rid)).deletedCount && void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); + await FileUpload.getStore('Avatars').deleteByRoomId(rid); + await callbacks.run('afterDeleteRoom', rid); - await Rooms.removeById(rid); - void notifyOnRoomChangedById(rid, 'removed'); + (await Rooms.removeById(rid)).deletedCount && void notifyOnRoomChangedById(rid, 'removed'); }; diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index 3af123a72bf7..92e7e4e32e13 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -19,7 +19,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { i18n } from '../../../../server/lib/i18n'; import { FileUpload } from '../../../file-upload/server'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById, notifyOnIntegrationChangedByUserId } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnIntegrationChangedByUserId, notifyOnSubscriptionChangedByUserId } from '../lib/notifyListener'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; @@ -93,7 +93,9 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele const rids = subscribedRooms.map((room) => room.rid); void notifyOnRoomChangedById(rids); - await Subscriptions.removeByUserId(userId); // Remove user subscriptions + // TODO: handle modifiedCount and/or return validity to call listener + await Subscriptions.removeByUserId(userId); + void notifyOnSubscriptionChangedByUserId(userId); if (user.roles.includes('livechat-agent')) { // Remove user as livechat agent diff --git a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts index 75b232462077..0ff082318838 100644 --- a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts +++ b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts @@ -1,18 +1,24 @@ import { Messages, Roles, Rooms, Subscriptions, ReadReceipts } from '@rocket.chat/models'; import { FileUpload } from '../../../file-upload/server'; +import { notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; -const bulkRoomCleanUp = async (rids: string[]): Promise => { - // no bulk deletion for files - await Promise.all(rids.map((rid) => FileUpload.removeFilesByRoomId(rid))); - - return Promise.all([ +const bulkRoomCleanUp = async (rids: string[]): Promise => { + const responses = await Promise.all([ Subscriptions.removeByRoomIds(rids), Messages.removeByRoomIds(rids), ReadReceipts.removeByRoomIds(rids), Rooms.removeByIds(rids), + // no bulk deletion for files + ...rids.map((rid) => FileUpload.removeFilesByRoomId(rid)), ]); + + if (responses[0].deletedCount) { + for await (const rid of rids) { + await notifyOnSubscriptionChangedByRoomId(rid); + } + } }; export const relinquishRoomOwnerships = async function ( diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts index e593b3508054..47fd28d6588e 100644 --- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts +++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts @@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback'; import { beforeLeaveRoomCallback } from '../../../../lib/callbacks/beforeLeaveRoomCallback'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByUserAndRoomId } from '../lib/notifyListener'; export const removeUserFromRoom = async function ( rid: string, @@ -59,7 +59,11 @@ export const removeUserFromRoom = async function ( await Message.saveSystemMessage('command', rid, 'survey', user); } - await Subscriptions.removeByRoomIdAndUserId(rid, user._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, user._id); + + if (deletedSubscription) { + void notifyOnSubscriptionChangedByUserAndRoomId(user._id, rid, 'removed'); + } if (room.teamId && room.teamMain) { await Team.removeMember(room.teamId, user._id); diff --git a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts index 4a0ac005e55c..8b513cd4382a 100644 --- a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts +++ b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts @@ -5,6 +5,7 @@ import type { UpdateFilter } from 'mongodb'; import { trim } from '../../../../lib/utils/stringUtils'; import { settings } from '../../../settings/server'; +import { notifyOnSubscriptionChangedByUserIdAndRoomType } from '../lib/notifyListener'; export const saveCustomFieldsWithoutValidation = async function (userId: string, formData: Record): Promise { if (trim(settings.get('Accounts_CustomFields')) !== '') { @@ -22,7 +23,8 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string, await Users.setCustomFields(userId, customFields); // Update customFields of all Direct Messages' Rooms for userId - await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields); + (await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields)).modifiedCount && + void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd'); for await (const fieldName of Object.keys(customFields)) { if (!customFieldsMeta[fieldName].modifyRecordField) { diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts index 0b9ff21e53e3..fcf30189f1cb 100644 --- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts +++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts @@ -3,7 +3,11 @@ import { Messages, VideoConference, LivechatDepartmentAgents, Rooms, Subscriptio import { SystemLogger } from '../../../../server/lib/logger/system'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnRoomChangedByUsernamesOrUids } from '../lib/notifyListener'; +import { + notifyOnRoomChangedByUsernamesOrUids, + notifyOnSubscriptionChangedByUserId, + notifyOnSubscriptionChangedByNameAndRoomType, +} from '../lib/notifyListener'; import { _setRealName } from './setRealName'; import { _setUsername } from './setUsername'; import { updateGroupDMsName } from './updateGroupDMsName'; @@ -129,20 +133,28 @@ async function updateUsernameReferences({ await Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername(msg._id, previousUsername, username, updatedMsg); } - await Rooms.replaceUsername(previousUsername, username); - await Rooms.replaceMutedUsername(previousUsername, username); - await Rooms.replaceUsernameOfUserByUserId(user._id, username); - await Subscriptions.setUserUsernameByUserId(user._id, username); + const responses = await Promise.all([ + Rooms.replaceUsername(previousUsername, username), + Rooms.replaceMutedUsername(previousUsername, username), + Rooms.replaceUsernameOfUserByUserId(user._id, username), + Subscriptions.setUserUsernameByUserId(user._id, username), + LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username), + ]); - await LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username); + if (responses[3]?.modifiedCount) { + void notifyOnSubscriptionChangedByUserId(user._id); + } - void notifyOnRoomChangedByUsernamesOrUids([user._id], [previousUsername, username]); + if (responses[0]?.modifiedCount || responses[1]?.modifiedCount || responses[2]?.modifiedCount) { + void notifyOnRoomChangedByUsernamesOrUids([user._id], [previousUsername, username]); + } } // update other references if either the name or username has changed if (usernameChanged || nameChanged) { // update name and fname of 1-on-1 direct messages - await Subscriptions.updateDirectNameAndFnameByName(previousUsername, rawUsername && username, rawName && name); + (await Subscriptions.updateDirectNameAndFnameByName(previousUsername, rawUsername && username, rawName && name)).modifiedCount && + void notifyOnSubscriptionChangedByNameAndRoomType(rawUsername && username, rawName && name, 'd'); // update name and fname of group direct messages await updateGroupDMsName(user); diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts index fabf59669450..170bf99f328e 100644 --- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts +++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts @@ -8,7 +8,11 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById, notifyOnRoomChangedByUserDM } from '../lib/notifyListener'; +import { + notifyOnRoomChangedById, + notifyOnRoomChangedByUserDM, + notifyOnSubscriptionChangedByUserIdAndRoomType, +} from '../lib/notifyListener'; import { closeOmnichannelConversations } from './closeOmnichannelConversations'; import { shouldRemoveOrChangeOwner, getSubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; @@ -38,8 +42,8 @@ async function reactivateDirectConversations(userId: string) { return acc; }, []); - await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false); - void notifyOnRoomChangedById(roomsToReactivate); + (await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false)).modifiedCount && + void notifyOnRoomChangedById(roomsToReactivate); } export async function setUserActiveStatus(userId: string, active: boolean, confirmRelinquish = false): Promise { @@ -101,13 +105,13 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi } if (user.username) { - await Subscriptions.setArchivedByUsername(user.username, !active); + (await Subscriptions.setArchivedByUsername(user.username, !active)).modifiedCount && + void notifyOnSubscriptionChangedByUserIdAndRoomType(user._id, 'd'); } if (active === false) { await Users.unsetLoginTokens(userId); - await Rooms.setDmReadOnlyByUserId(userId, undefined, true, false); - void notifyOnRoomChangedByUserDM(userId); + (await Rooms.setDmReadOnlyByUserId(userId, undefined, true, false)).modifiedCount && void notifyOnRoomChangedByUserDM(userId); } else { await Users.unsetReason(userId); await reactivateDirectConversations(userId); diff --git a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts index 7db86ed933a3..59d15ada1213 100644 --- a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts +++ b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts @@ -2,11 +2,13 @@ import { Message } from '@rocket.chat/core-services'; import type { IMessage } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions } from '@rocket.chat/models'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; export const unarchiveRoom = async function (rid: string, user: IMessage['u']): Promise { await Rooms.unarchiveById(rid); - await Subscriptions.unarchiveByRoomId(rid); + + (await Subscriptions.unarchiveByRoomId(rid)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); + await Message.saveSystemMessage('room-unarchived', rid, '', user); void notifyOnRoomChangedById(rid); diff --git a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts index a0ad2eedcf55..d548e5f48c0b 100644 --- a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts +++ b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts @@ -1,6 +1,8 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; +import { notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; + const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', '); const getName = (members: IUser[]): string => members.map(({ username }) => username).join(','); @@ -63,7 +65,8 @@ export const updateGroupDMsName = async (userThatChangedName: IUser): Promise _id !== sub.u._id); - await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers)); + (await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers))).modifiedCount && + void notifyOnSubscriptionChangedByRoomId(room._id); } } }; diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 38b95404a13e..4ab858166385 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -13,6 +13,7 @@ import type { IEmailInbox, IIntegrationHistory, AtLeast, + ISubscription, } from '@rocket.chat/core-typings'; import { Rooms, @@ -23,6 +24,7 @@ import { Integrations, LoginServiceConfiguration, IntegrationHistory, + Subscriptions, } from '@rocket.chat/models'; type ClientAction = 'inserted' | 'updated' | 'removed'; @@ -307,3 +309,293 @@ export async function notifyOnIntegrationHistoryChangedById { +// if (!dbWatchersDisabled) { +// return; +// } + +// void api.broadcast('watch.subscriptions', { clientAction, subscription }); +// } + +// export async function notifyOnSubscriptionChangedById( +// _id: ISubscription['_id'], +// clientAction: ClientAction = 'updated', +// ): Promise { +// if (!dbWatchersDisabled) { +// return; +// } + +// const subscription = clientAction === 'removed' ? await Subscriptions.trashFindOneById(_id) : await Subscriptions.findOneById(_id); + +// if (!subscription) { +// return; +// } + +// void api.broadcast('watch.subscriptions', { clientAction, subscription }); +// } + +export async function notifyOnSubscriptionChangedByUserAndRoomId( + uid: ISubscription['u']['_id'], + rid: ISubscription['rid'], + clientAction: ClientAction = 'updated', + // diff?: Partial, +): Promise { + // || !hasSubscriptionFields(diff) + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ rid, 'u._id': uid }, { projection: subscriptionFields }) + : Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); + + if (!subscriptions) { + return; + } + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedById(id: ISubscription['_id'], clientAction: ClientAction = 'updated'): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscription = clientAction === 'removed' ? await Subscriptions.trashFindOneById(id) : await Subscriptions.findOneById(id); + + if (!subscription) { + return; + } + + void api.broadcast('watch.subscriptions', { clientAction, subscription }); +} + +export async function notifyOnSubscriptionChangedByRoomIdExcludingUserIds( + rid: ISubscription['rid'], + uids: ISubscription['u']['_id'][], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByRoomIdExcludingUserIds(rid, uids, { projection: subscriptionFields }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByUserPreferences( + uid: ISubscription['u']['_id'], + notificationOriginField: keyof ISubscription, + originFieldNotEqualValue: 'user' | 'subscription', + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, { + projection: subscriptionFields, + }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByRoomId( + rid: ISubscription['rid'], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ rid }, { projection: subscriptionFields }) + : Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); + + if (!subscriptions) { + return; + } + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByToken(token: string, clientAction: ClientAction = 'updated'): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ 'v.token': token }, { projection: subscriptionFields }) + : Subscriptions.findByToken(token, { projection: subscriptionFields }); + + if (!subscriptions) { + return; + } + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByAutoTranslateAndUserId( + uid: ISubscription['u']['_id'], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByUserIdAndRoomType( + uid: ISubscription['u']['_id'], + t: ISubscription['t'], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByNameAndRoomType( + name?: ISubscription['name'], + fname?: ISubscription['fname'], + t?: ISubscription['t'], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByNameAndRoomType(name, fname, t, { projection: subscriptionFields }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByUserId( + uid: ISubscription['u']['_id'], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByUserId(uid, { projection: subscriptionFields }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +export async function notifyOnSubscriptionChangedByRoomIdAndUserIds( + rid: ISubscription['rid'], + uids: ISubscription['u']['_id'][], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ rid, 'u._id': { $in: uids } }, { projection: subscriptionFields }) + : Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); + + if (!subscriptions) { + return; + } + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + +const subscriptionFields = { + t: 1, + ts: 1, + ls: 1, + lr: 1, + name: 1, + fname: 1, + rid: 1, + code: 1, + f: 1, + u: 1, + open: 1, + alert: 1, + roles: 1, + unread: 1, + prid: 1, + userMentions: 1, + groupMentions: 1, + archived: 1, + audioNotificationValue: 1, + desktopNotifications: 1, + mobilePushNotifications: 1, + emailNotifications: 1, + desktopPrefOrigin: 1, + mobilePrefOrigin: 1, + emailPrefOrigin: 1, + unreadAlert: 1, + // _updatedAt: 1, + blocked: 1, + blocker: 1, + autoTranslate: 1, + autoTranslateLanguage: 1, + disableNotifications: 1, + hideUnreadStatus: 1, + hideMentionStatus: 1, + muteGroupMentions: 1, + ignored: 1, + E2EKey: 1, + E2ESuggestedKey: 1, + tunread: 1, + tunreadGroup: 1, + tunreadUser: 1, + + // Omnichannel fields + department: 1, + v: 1, + onHold: 1, +}; + +// function hasKeys(requiredKeys: string[]): (data?: Record) => boolean { +// return (data?: Record): boolean => { +// if (!data) { +// return false; +// } + +// return requiredKeys.some((key) => key in data); +// }; +// } + +// const hasSubscriptionFields = hasKeys(Object.keys(subscriptionFields)); diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 76a18eba3362..889be206b9a0 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -3,9 +3,11 @@ import { isEditedMessage } from '@rocket.chat/core-typings'; import { Subscriptions, Rooms } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import moment from 'moment'; +import type { Document, UpdateResult } from 'mongodb'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; +import { notifyOnSubscriptionChangedByRoomIdExcludingUserIds, notifyOnSubscriptionChangedByRoomIdAndUserIds } from './notifyListener'; function messageContainsHighlight(message: IMessage, highlights: string[]): boolean { if (!highlights || highlights.length === 0) return false; @@ -55,10 +57,10 @@ const incGroupMentions = async ( roomType: RoomType, excludeUserId: IUser['_id'], unreadCount: Exclude, -): Promise => { +): Promise => { const incUnreadByGroup = ['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0; - await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread); + return Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread); }; const incUserMentions = async ( @@ -66,10 +68,10 @@ const incUserMentions = async ( roomType: RoomType, uids: IUser['_id'][], unreadCount: Exclude, -): Promise => { +): Promise => { const incUnreadByUser = new Set(['all_messages', 'user_mentions_only', 'user_and_group_mentions_only']).has(unreadCount); const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0; - await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread); + return Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread); }; export const getUserIdsFromHighlights = async (rid: IRoom['_id'], message: IMessage): Promise => { @@ -100,45 +102,79 @@ const getUnreadSettingCount = (roomType: RoomType): UnreadCountType => { }; async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise { - // Don't increase unread counter on thread messages - if (room != null && !message.tmid) { - const { toAll, toHere, mentionIds } = await getMentions(message); - - const userIds = new Set(mentionIds); - - const unreadCount = getUnreadSettingCount(room.t); - - (await getUserIdsFromHighlights(room._id, message)).forEach((uid) => userIds.add(uid)); + if (!room || message.tmid) { + return; + } - // Give priority to user mentions over group mentions - if (userIds.size > 0) { - await incUserMentions(room._id, room.t, [...userIds], unreadCount as Exclude); - } else if (toAll || toHere) { - await incGroupMentions(room._id, room.t, message.u._id, unreadCount as Exclude); + const [mentions, highlightIds] = await Promise.all([getMentions(message), getUserIdsFromHighlights(room._id, message)]); + + const { toAll, toHere, mentionIds } = mentions; + const userIds = [...new Set([...mentionIds, ...highlightIds])]; + const unreadCount = getUnreadSettingCount(room.t); + + const includeUsers = new Set(); + const excludeUsers = new Set(); + + // Give priority to user mentions over group mentions + if (userIds.length) { + const incUserMentionsResponse = await incUserMentions( + room._id, + room.t, + userIds, + unreadCount as Exclude, + ); + if (incUserMentionsResponse.modifiedCount) { + userIds.forEach((userId) => includeUsers.add(userId)); } + } else if (toAll || toHere) { + const incGroupMentionsResponse = await incGroupMentions( + room._id, + room.t, + message.u._id, + unreadCount as Exclude, + ); + if (incGroupMentionsResponse.modifiedCount) { + excludeUsers.add(message.u._id); + } + } - // this shouldn't run only if has group mentions because it will already exclude mentioned users from the query - if (!toAll && !toHere && unreadCount === 'all_messages') { - await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); + if (!toAll && !toHere && unreadCount === 'all_messages') { + const incUnreadForRoomIdResponse = await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); + if (incUnreadForRoomIdResponse.modifiedCount) { + [userIds, message.u._id].flat().forEach((userId) => excludeUsers.add(userId)); } } - // Update all other subscriptions to alert their owners but without incrementing - // the unread counter, as it is only for mentions and direct messages - // We now set alert and open properties in two separate update commands. This proved to be more efficient on MongoDB - because it uses a more efficient index. - await Promise.all([ + const [alertResponse, openResponse] = await Promise.all([ Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id), Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id), ]); + + if (alertResponse.modifiedCount || openResponse.modifiedCount) { + excludeUsers.add(message.u._id); + } + + if (includeUsers.size) { + void notifyOnSubscriptionChangedByRoomIdExcludingUserIds(message.rid, [...excludeUsers]); + } + + if (excludeUsers.size) { + void notifyOnSubscriptionChangedByRoomIdAndUserIds(message.rid, [...includeUsers]); + } } export async function updateThreadUsersSubscriptions(message: IMessage, replies: IUser['_id'][]): Promise { // Don't increase unread counter on thread messages - - await Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies); const repliesPlusSender = [...new Set([message.u._id, ...replies])]; - await Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender); - await Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date()); + + const responses = await Promise.all([ + Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies), + Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender), + Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date()), + ]); + + responses.some((response) => response?.modifiedCount) && + void notifyOnSubscriptionChangedByRoomIdAndUserIds(message.rid, repliesPlusSender); } export async function notifyUsersOnMessage(message: IMessage, room: IRoom): Promise { diff --git a/apps/meteor/app/lib/server/methods/blockUser.ts b/apps/meteor/app/lib/server/methods/blockUser.ts index b65423edf25b..903a6709a786 100644 --- a/apps/meteor/app/lib/server/methods/blockUser.ts +++ b/apps/meteor/app/lib/server/methods/blockUser.ts @@ -5,6 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -33,14 +34,22 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); - const subscription2 = await Subscriptions.findOneByRoomIdAndUserId(rid, blocked); + const [blockedUser, blockerUser] = await Promise.all([ + Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }), + Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }), + ]); - if (!subscription || !subscription2) { + if (!blockedUser || !blockerUser) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } - await Subscriptions.setBlockedByRoomId(rid, blocked, userId); + const [blockedResponse, blockerResponse] = await Subscriptions.setBlockedByRoomId(rid, blocked, userId); + + const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])]; + + if (listenerUsers.length) { + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers); + } return true; }, diff --git a/apps/meteor/app/lib/server/methods/unblockUser.ts b/apps/meteor/app/lib/server/methods/unblockUser.ts index 6c8a9d486bab..1ab5ec670d32 100644 --- a/apps/meteor/app/lib/server/methods/unblockUser.ts +++ b/apps/meteor/app/lib/server/methods/unblockUser.ts @@ -3,6 +3,8 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -20,14 +22,22 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'blockUser' }); } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); - const subscription2 = await Subscriptions.findOneByRoomIdAndUserId(rid, blocked); + const [blockedUser, blockerUser] = await Promise.all([ + Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }), + Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }), + ]); - if (!subscription || !subscription2) { + if (!blockedUser || !blockerUser) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } - await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId); + const [blockedResponse, blockerResponse] = await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId); + + const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])]; + + if (listenerUsers.length) { + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers); + } return true; }, diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index 4105ae8f500f..47fe5492e0aa 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -6,7 +6,7 @@ import type { MatchKeysAndValues, OnlyFieldsOfType } from 'mongodb'; import { callbacks } from '../../../../lib/callbacks'; import { trim } from '../../../../lib/utils/stringUtils'; -import { notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import { i18n } from '../../../utils/lib/i18n'; type RegisterContactProps = { @@ -138,13 +138,19 @@ export const Contacts = { for await (const room of rooms) { const { _id: rid } = room; - await Promise.all([ + const responses = await Promise.all([ Rooms.setFnameById(rid, name), LivechatInquiry.setNameByRoomId(rid, name), Subscriptions.updateDisplayNameByRoomId(rid, name), ]); - void notifyOnRoomChangedById(rid); + if (responses[2]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + + if (responses[0]?.modifiedCount) { + void notifyOnRoomChangedById(rid); + } } } diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 453869d4425a..817e0ae38a7b 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -37,6 +37,7 @@ import { i18n } from '../../../../server/lib/i18n'; import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { sendNotification } from '../../../lib/server'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; +import { notifyOnSubscriptionChangedByRoomId, notifyOnSubscriptionChangedByUserAndRoomId } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { Livechat as LivechatTyped } from './LivechatTyped'; import { queueInquiry, saveQueueInquiry } from './QueueManager'; @@ -269,7 +270,12 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username } return; } - await Subscriptions.removeByRoomIdAndUserId(rid, _id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, _id); + + if (deletedSubscription) { + void notifyOnSubscriptionChangedByUserAndRoomId(_id, rid, 'removed'); + } + await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name }); setImmediate(() => { @@ -479,7 +485,11 @@ export const updateChatDepartment = async ({ LivechatRooms.changeDepartmentIdByRoomId(rid, newDepartmentId), LivechatInquiry.changeDepartmentIdByRoomId(rid, newDepartmentId), Subscriptions.changeDepartmentByRoomId(rid, newDepartmentId), - ]); + ]).then((data) => { + if (data[2].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + }); setImmediate(() => { void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 8af9980eb5e6..de9b57047dd0 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -57,7 +57,7 @@ import { FileUpload } from '../../../file-upload/server'; import { deleteMessage } from '../../../lib/server/functions/deleteMessage'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; -import { notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; import { settings } from '../../../settings/server'; @@ -303,7 +303,7 @@ class LivechatClass { throw new Error('Error closing room'); } - await Subscriptions.removeByRoomId(rid); + (await Subscriptions.removeByRoomId(rid)).deletedCount && void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); this.logger.debug(`DB updated for room ${room._id}`); @@ -508,6 +508,10 @@ class LivechatClass { LivechatRooms.removeById(rid), ]); + if (result[2]?.status === 'fulfilled' && result[2].value?.deletedCount) { + void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); + } + for (const r of result) { if (r.status === 'rejected') { this.logger.error(`Error removing room ${rid}: ${r.reason}`); @@ -1269,7 +1273,11 @@ class LivechatClass { Subscriptions.removeByVisitorToken(token), LivechatRooms.removeByVisitorToken(token), LivechatInquiry.removeByVisitorToken(token), - ]); + ]).then((data) => { + if (data[0]?.deletedCount) { + void notifyOnSubscriptionChangedByToken(token, 'removed'); + } + }); } async deleteMessage({ guest, message }: { guest: ILivechatVisitor; message: IMessage }) { @@ -1804,11 +1812,16 @@ class LivechatClass { if (guestData?.name?.trim().length) { const { _id: rid } = roomData; const { name } = guestData; - await Promise.all([ + + const responses = await Promise.all([ Rooms.setFnameById(rid, name), LivechatInquiry.setNameByRoomId(rid, name), Subscriptions.updateDisplayNameByRoomId(rid, name), ]); + + if (responses[2]?.modifiedCount) { + await notifyOnSubscriptionChangedByRoomId(rid); + } } void notifyOnRoomChangedById(roomData._id); diff --git a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts index 2ba0224a0c70..cbf3f488c90d 100644 --- a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts +++ b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts @@ -5,6 +5,8 @@ import { Meteor } from 'meteor/meteor'; import logger from './logger'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../lib/server/lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -36,7 +38,9 @@ Meteor.methods({ }); } - await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts); + (await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(userId, lastMessage.rid); + return; } @@ -72,7 +76,9 @@ Meteor.methods({ if (firstUnreadMessage.ts >= lastSeen) { return logger.debug('Provided message is already marked as unread'); } - logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`); - await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts); + + logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`); + (await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(userId, originalMessage.rid); }, }); diff --git a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts index a4376c709275..601ecbecd561 100644 --- a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts +++ b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts @@ -4,6 +4,7 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; import { getUserNotificationPreference } from '../../../utils/server/getUserNotificationPreference'; const saveAudioNotificationValue = (subId: ISubscription['_id'], value: string) => @@ -132,7 +133,7 @@ Meteor.methods({ }); } - await notifications[field].updateMethod(subscription, value); + (await notifications[field].updateMethod(subscription, value)).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); return true; }, @@ -144,13 +145,16 @@ Meteor.methods({ method: 'saveAudioNotificationValue', }); } + const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); if (!subscription) { throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'saveAudioNotificationValue', }); } - await saveAudioNotificationValue(subscription._id, value); + + (await saveAudioNotificationValue(subscription._id, value)).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); + return true; }, }); diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index 30daef8b8b93..b9f120fa7d89 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -4,49 +4,56 @@ import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocke import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; +import { + notifyOnSubscriptionChangedByRoomIdAndUserIds, + notifyOnSubscriptionChangedByUserAndRoomId, +} from '../../lib/server/lib/notifyListener'; + export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { - const { rid, ts, u } = message; if (!tmid || isEditedMessage(message)) { return false; } - const { toAll, toHere, mentionIds } = await getMentions(message); + const { rid, ts, u } = message; + + const [highlightsUids, threadFollowers, { toAll, toHere, mentionIds }] = await Promise.all([ + getUserIdsFromHighlights(rid, message), + Messages.getThreadFollowsByThreadId(tmid), + getMentions(message), + ]); const addToReplies = [ - ...new Set([ - ...followers, - ...mentionIds, - ...(Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]), - ]), + ...new Set([...followers, ...mentionIds, ...(parentMessage.replies?.length ? [u._id] : [parentMessage.u._id, u._id])]), ]; - const highlightedUserIds = new Set(); - (await getUserIdsFromHighlights(rid, message)).forEach((uid) => highlightedUserIds.add(uid)); - await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); - await ReadReceipts.setAsThreadById(tmid); + const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIds.includes(userId)) || []; - const replies = await Messages.getThreadFollowsByThreadId(tmid); + // Notify everyone involved in the thread + const notifyOptions = toAll || toHere ? { groupMention: true } : {}; - const repliesFiltered = (replies || []).filter((userId) => userId !== u._id).filter((userId) => !mentionIds.includes(userId)); + // Notify message mentioned users and highlights + const mentionedUsers = [...new Set([...mentionIds, ...highlightsUids])]; - if (toAll || toHere) { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, { - groupMention: true, - }); - } else { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, {}); - } + const promises = [ + Messages.updateRepliesByThreadId(tmid, addToReplies, ts), + ReadReceipts.setAsThreadById(tmid), + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, threadFollowersUids, tmid, notifyOptions), + ]; - const mentionedUsers = new Set([...mentionIds, ...highlightedUserIds]); - for await (const userId of mentionedUsers) { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, [userId], tmid, { userMention: true }); + if (mentionedUsers.length) { + promises.push(Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, mentionedUsers, tmid, { userMention: true })); } - const highlightIds = Array.from(highlightedUserIds); - if (highlightIds.length) { - await Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightIds); - await Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightIds); + if (highlightsUids.length) { + promises.push( + Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightsUids), + Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightsUids), + ); } + + await Promise.allSettled(promises); + + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUids]); } export async function follow({ tmid, uid }: { tmid: string; uid: string }) { @@ -62,20 +69,27 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; return false; } - await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); + if (removeUnreadThreadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + } await Messages.removeThreadFollowerByThreadId(tmid, uid); } export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: string; tmid: string }) => { - const projection = { tunread: 1 }; - const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection }); + const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { tunread: 1 } }); if (!sub) { return; } + // if the thread being marked as read is the last one unread also clear the unread subscription flag const clearAlert = sub.tunread && sub.tunread?.length <= 1 && sub.tunread.includes(tmid); - await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); + if (removeUnreadThreadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(userId, rid); + } + await NotificationQueue.clearQueueByUserId(userId); }; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts index 4e76f396617b..c851aa6033d2 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts @@ -1,6 +1,7 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatRooms, Subscriptions } from '@rocket.chat/models'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../../../app/lib/server/lib/notifyListener'; import { settings } from '../../../../../app/settings/server'; import { callbacks } from '../../../../../lib/callbacks'; import { AutoCloseOnHoldScheduler } from '../lib/AutoCloseOnHoldScheduler'; @@ -20,7 +21,11 @@ const onCloseLivechat = async (params: LivechatCloseCallbackParams) => { LivechatRooms.unsetOnHoldByRoomId(roomId), Subscriptions.unsetOnHoldByRoomId(roomId), AutoCloseOnHoldScheduler.unscheduleRoom(roomId), - ]); + ]).then((data) => { + if (data[1].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId); + } + }); if (!settings.get('Livechat_waiting_queue')) { return params; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts index 980d0e644a99..8ef146d466f8 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts @@ -5,7 +5,10 @@ import type { IOmnichannelRoom, IUser, ILivechatInquiryRecord, IOmnichannelSyste import { Logger } from '@rocket.chat/logger'; import { LivechatRooms, Subscriptions, LivechatInquiry } from '@rocket.chat/models'; -import { notifyOnRoomChangedById } from '../../../../../app/lib/server/lib/notifyListener'; +import { + notifyOnRoomChangedById, + notifyOnSubscriptionChangedByRoomId, +} from '../../../../../app/lib/server/lib/notifyListener'; import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper'; import { queueInquiry } from '../../../../../app/livechat/server/lib/QueueManager'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; @@ -57,11 +60,17 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE LivechatRooms.setOnHoldByRoomId(roomId), Subscriptions.setOnHoldByRoomId(roomId), Message.saveSystemMessage('omnichannel_placed_chat_on_hold', roomId, '', onHoldBy, { comment }), - ]); + ]).then((data) => { + if (data[0].modifiedCount) { + void notifyOnRoomChangedById(roomId); + } - await callbacks.run('livechat:afterOnHold', room); + if (data[1].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId); + } + }); - void notifyOnRoomChangedById(roomId); + await callbacks.run('livechat:afterOnHold', room); } async resumeRoomOnHold( @@ -108,11 +117,17 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE LivechatRooms.unsetOnHoldByRoomId(roomId), Subscriptions.unsetOnHoldByRoomId(roomId), Message.saveSystemMessage('omnichannel_on_hold_chat_resumed', roomId, '', resumeBy, { comment }), - ]); + ]).then((data) => { + if (data[0].modifiedCount) { + void notifyOnRoomChangedById(roomId); + } - await callbacks.run('livechat:afterOnHoldChatResumed', room); + if (data[1].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId); + } + }); - void notifyOnRoomChangedById(roomId); + await callbacks.run('livechat:afterOnHoldChatResumed', room); } private async attemptToAssignRoomToServingAgentElseQueueIt({ diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts index 5b7a720ba312..d0ff679ba4cb 100644 --- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts +++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts @@ -2,6 +2,7 @@ import type { IRoom, IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; import { Subscriptions } from '@rocket.chat/models'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../../../../app/lib/server/lib/notifyListener'; import { callbacks } from '../../../../../lib/callbacks'; import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt'; @@ -15,7 +16,8 @@ callbacks.add( if (!isOmnichannelRoom(room) || !room.closedAt) { // set subscription as read right after message was sent - await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id); + (await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(message.u._id, room._id); } // mark message as read as well diff --git a/apps/meteor/server/database/watchCollections.ts b/apps/meteor/server/database/watchCollections.ts index 31691aab3b88..3d7dd0af3b12 100644 --- a/apps/meteor/server/database/watchCollections.ts +++ b/apps/meteor/server/database/watchCollections.ts @@ -35,7 +35,6 @@ export function getWatchCollections(): string[] { LivechatDepartmentAgents.getCollectionName(), InstanceStatus.getCollectionName(), Settings.getCollectionName(), - Subscriptions.getCollectionName(), ]; // add back to the list of collections in case db watchers are enabled @@ -50,6 +49,7 @@ export function getWatchCollections(): string[] { collections.push(LoginServiceConfiguration.getCollectionName()); collections.push(EmailInbox.getCollectionName()); collections.push(IntegrationHistory.getCollectionName()); + collections.push(Subscriptions.getCollectionName()); } if (onlyCollections.length > 0) { diff --git a/apps/meteor/server/lib/readMessages.ts b/apps/meteor/server/lib/readMessages.ts index d7c8cf559288..95506aa46a9e 100644 --- a/apps/meteor/server/lib/readMessages.ts +++ b/apps/meteor/server/lib/readMessages.ts @@ -1,6 +1,7 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; import { NotificationQueue, Subscriptions } from '@rocket.chat/models'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; import { callbacks } from '../../lib/callbacks'; export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThreads: boolean): Promise { @@ -15,7 +16,8 @@ export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThr // do not mark room as read if there are still unread threads const alert = !!(sub.alert && !readThreads && sub.tunread && sub.tunread.length > 0); - await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert); + (await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); await NotificationQueue.clearQueueByUserId(uid); diff --git a/apps/meteor/server/lib/resetUserE2EKey.ts b/apps/meteor/server/lib/resetUserE2EKey.ts index b6c2d2c886e3..0949602b953a 100644 --- a/apps/meteor/server/lib/resetUserE2EKey.ts +++ b/apps/meteor/server/lib/resetUserE2EKey.ts @@ -2,6 +2,7 @@ import { api } from '@rocket.chat/core-services'; import { Subscriptions, Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedByUserId } from '../../app/lib/server/lib/notifyListener'; import * as Mailer from '../../app/mailer/server/api'; import { settings } from '../../app/settings/server'; import { i18n } from './i18n'; @@ -66,11 +67,13 @@ export async function resetUserE2EEncriptionKey(uid: string, notifyUser: boolean } // force logout the live sessions - await api.broadcast('user.forceLogout', uid); - await Users.resetE2EKey(uid); - await Subscriptions.resetUserE2EKey(uid); + const responses = await Promise.all([Users.resetE2EKey(uid), Subscriptions.resetUserE2EKey(uid)]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByUserId(uid); + } // Force the user to logout, so that the keys can be generated again await Users.unsetLoginTokens(uid); diff --git a/apps/meteor/server/methods/addAllUserToRoom.ts b/apps/meteor/server/methods/addAllUserToRoom.ts index 0c4f1532371d..18ecaf6bb5fe 100644 --- a/apps/meteor/server/methods/addAllUserToRoom.ts +++ b/apps/meteor/server/methods/addAllUserToRoom.ts @@ -6,6 +6,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; import { getDefaultSubscriptionPref } from '../../app/utils/lib/getDefaultSubscriptionPref'; import { callbacks } from '../../lib/callbacks'; @@ -58,7 +59,7 @@ Meteor.methods({ } await callbacks.run('beforeJoinRoom', user, room); const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user); - await Subscriptions.createWithRoomAndUser(room, user, { + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, { ts: now, open: true, alert: true, @@ -68,6 +69,9 @@ Meteor.methods({ ...autoTranslateConfig, ...getDefaultSubscriptionPref(user), }); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } await Message.saveSystemMessage('uj', rid, user.username || '', user, { ts: now }); await callbacks.run('afterJoinRoom', user, room); } diff --git a/apps/meteor/server/methods/addRoomLeader.ts b/apps/meteor/server/methods/addRoomLeader.ts index d8c9d1a7351f..520854a3a65c 100644 --- a/apps/meteor/server/methods/addRoomLeader.ts +++ b/apps/meteor/server/methods/addRoomLeader.ts @@ -6,6 +6,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; declare module '@rocket.chat/ui-contexts' { @@ -56,7 +57,7 @@ Meteor.methods({ }); } - await Subscriptions.addRoleById(subscription._id, 'leader'); + (await Subscriptions.addRoleById(subscription._id, 'leader')).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); const fromUser = await Users.findOneById(uid); diff --git a/apps/meteor/server/methods/addRoomModerator.ts b/apps/meteor/server/methods/addRoomModerator.ts index 357629a8155e..db404af3cf41 100644 --- a/apps/meteor/server/methods/addRoomModerator.ts +++ b/apps/meteor/server/methods/addRoomModerator.ts @@ -7,6 +7,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; declare module '@rocket.chat/ui-contexts' { @@ -64,7 +65,8 @@ Meteor.methods({ }); } - await Subscriptions.addRoleById(subscription._id, 'moderator'); + (await Subscriptions.addRoleById(subscription._id, 'moderator')).modifiedCount && + void notifyOnSubscriptionChangedById(subscription._id); const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/addRoomOwner.ts b/apps/meteor/server/methods/addRoomOwner.ts index 5ee7a9dd74d2..0baf316d12e9 100644 --- a/apps/meteor/server/methods/addRoomOwner.ts +++ b/apps/meteor/server/methods/addRoomOwner.ts @@ -7,6 +7,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; declare module '@rocket.chat/ui-contexts' { @@ -64,7 +65,7 @@ Meteor.methods({ }); } - await Subscriptions.addRoleById(subscription._id, 'owner'); + (await Subscriptions.addRoleById(subscription._id, 'owner')).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/hideRoom.ts b/apps/meteor/server/methods/hideRoom.ts index 64397969cca0..5955fcd747eb 100644 --- a/apps/meteor/server/methods/hideRoom.ts +++ b/apps/meteor/server/methods/hideRoom.ts @@ -3,6 +3,8 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -19,7 +21,13 @@ export const hideRoomMethod = async (userId: string, rid: string): Promise({ diff --git a/apps/meteor/server/methods/ignoreUser.ts b/apps/meteor/server/methods/ignoreUser.ts index 980efbd31ba4..94349919bb0b 100644 --- a/apps/meteor/server/methods/ignoreUser.ts +++ b/apps/meteor/server/methods/ignoreUser.ts @@ -3,6 +3,8 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -23,7 +25,10 @@ Meteor.methods({ }); } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); + const [subscription, subscriptionIgnoredUser] = await Promise.all([ + Subscriptions.findOneByRoomIdAndUserId(rid, userId), + Subscriptions.findOneByRoomIdAndUserId(rid, ignoredUser), + ]); if (!subscription) { throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { @@ -31,14 +36,18 @@ Meteor.methods({ }); } - const subscriptionIgnoredUser = await Subscriptions.findOneByRoomIdAndUserId(rid, ignoredUser); - if (!subscriptionIgnoredUser) { throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'ignoreUser', }); } - return !!(await Subscriptions.ignoreUser({ _id: subscription._id, ignoredUser, ignore })); + const result = await Subscriptions.ignoreUser({ _id: subscription._id, ignoredUser, ignore }); + + if (result.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } + + return !!result; }, }); diff --git a/apps/meteor/server/methods/openRoom.ts b/apps/meteor/server/methods/openRoom.ts index 3348104862b5..d445fbdee65e 100644 --- a/apps/meteor/server/methods/openRoom.ts +++ b/apps/meteor/server/methods/openRoom.ts @@ -4,6 +4,8 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -23,6 +25,12 @@ Meteor.methods({ }); } - return (await Subscriptions.openByRoomIdAndUserId(rid, uid)).modifiedCount; + const openByRoomResponse = await Subscriptions.openByRoomIdAndUserId(rid, uid); + + if (openByRoomResponse.modifiedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + } + + return openByRoomResponse.modifiedCount; }, }); diff --git a/apps/meteor/server/methods/removeRoomLeader.ts b/apps/meteor/server/methods/removeRoomLeader.ts index 40b217e2b793..19233e1654af 100644 --- a/apps/meteor/server/methods/removeRoomLeader.ts +++ b/apps/meteor/server/methods/removeRoomLeader.ts @@ -6,6 +6,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; declare module '@rocket.chat/ui-contexts' { @@ -56,7 +57,8 @@ Meteor.methods({ }); } - await Subscriptions.removeRoleById(subscription._id, 'leader'); + (await Subscriptions.removeRoleById(subscription._id, 'leader')).modifiedCount && + void notifyOnSubscriptionChangedById(subscription._id); const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/removeRoomModerator.ts b/apps/meteor/server/methods/removeRoomModerator.ts index a0d24159e4c8..89ab0debbc10 100644 --- a/apps/meteor/server/methods/removeRoomModerator.ts +++ b/apps/meteor/server/methods/removeRoomModerator.ts @@ -7,6 +7,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; declare module '@rocket.chat/ui-contexts' { @@ -64,7 +65,8 @@ Meteor.methods({ }); } - await Subscriptions.removeRoleById(subscription._id, 'moderator'); + (await Subscriptions.removeRoleById(subscription._id, 'moderator')).modifiedCount && + void notifyOnSubscriptionChangedById(subscription._id); const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/removeRoomOwner.ts b/apps/meteor/server/methods/removeRoomOwner.ts index 37311898177c..da2607933272 100644 --- a/apps/meteor/server/methods/removeRoomOwner.ts +++ b/apps/meteor/server/methods/removeRoomOwner.ts @@ -7,6 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { getUsersInRole } from '../../app/authorization/server'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; declare module '@rocket.chat/ui-contexts' { @@ -71,7 +72,7 @@ Meteor.methods({ }); } - await Subscriptions.removeRoleById(subscription._id, 'owner'); + (await Subscriptions.removeRoleById(subscription._id, 'owner')).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/removeUserFromRoom.ts b/apps/meteor/server/methods/removeUserFromRoom.ts index ee1ba25e76f4..eb796e657199 100644 --- a/apps/meteor/server/methods/removeUserFromRoom.ts +++ b/apps/meteor/server/methods/removeUserFromRoom.ts @@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync, getUsersInRole } from '../../app/authorization/server'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../app/authorization/server/functions/hasRole'; -import { notifyOnRoomChanged } from '../../app/lib/server/lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; import { RoomMemberActions } from '../../definition/IRoomTypeConfig'; import { callbacks } from '../../lib/callbacks'; import { afterRemoveFromRoomCallback } from '../../lib/callbacks/afterRemoveFromRoomCallback'; @@ -76,7 +76,11 @@ export const removeUserFromRoomMethod = async (fromId: string, data: { rid: stri await callbacks.run('beforeRemoveFromRoom', { removedUser, userWhoRemoved: fromUser }, room); - await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id); + + if (deletedSubscription) { + void notifyOnSubscriptionChangedByUserAndRoomId(removedUser._id, data.rid, 'removed'); + } if (['c', 'p'].includes(room.t) === true) { await removeUserFromRolesAsync(removedUser._id, ['moderator', 'owner'], data.rid); diff --git a/apps/meteor/server/methods/saveUserPreferences.ts b/apps/meteor/server/methods/saveUserPreferences.ts index c23f466cf8a5..5bd1ebe63899 100644 --- a/apps/meteor/server/methods/saveUserPreferences.ts +++ b/apps/meteor/server/methods/saveUserPreferences.ts @@ -1,3 +1,4 @@ +import type { ISubscription } from '@rocket.chat/core-typings'; import { Subscriptions, Users } from '@rocket.chat/models'; import type { FontSize } from '@rocket.chat/rest-typings'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -5,6 +6,11 @@ import type { ThemePreference } from '@rocket.chat/ui-theming/src/types/themes'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { + notifyOnSubscriptionChangedByAutoTranslateAndUserId, + notifyOnSubscriptionChangedByUserId, + notifyOnSubscriptionChangedByUserPreferences, +} from '../../app/lib/server/lib/notifyListener'; import { settings as rcSettings } from '../../app/settings/server'; type UserPreferences = { @@ -53,6 +59,27 @@ declare module '@rocket.chat/ui-contexts' { } } +async function updateNotificationPreferences( + userId: ISubscription['u']['_id'], + setting: keyof ISubscription, + newValue: string, + oldValue: string, + preferenceType: keyof ISubscription, +) { + if (newValue === oldValue) { + return; + } + + if (newValue === 'default') { + (await Subscriptions.clearNotificationUserPreferences(userId, setting, preferenceType)).modifiedCount && + void notifyOnSubscriptionChangedByUserPreferences(userId, preferenceType, 'user'); + return; + } + + (await Subscriptions.updateNotificationUserPreferences(userId, newValue, setting, preferenceType)).modifiedCount && + void notifyOnSubscriptionChangedByUserPreferences(userId, preferenceType, 'subscription'); +} + export const saveUserPreferences = async (settings: Partial, userId: string): Promise => { const keys = { language: Match.Optional(String), @@ -128,53 +155,42 @@ export const saveUserPreferences = async (settings: Partial, us await Users.setPreferences(user._id, settings); - // propagate changed notification preferences setImmediate(async () => { - if (settings.desktopNotifications && oldDesktopNotifications !== settings.desktopNotifications) { - if (settings.desktopNotifications === 'default') { - await Subscriptions.clearNotificationUserPreferences(user._id, 'desktopNotifications', 'desktopPrefOrigin'); - } else { - await Subscriptions.updateNotificationUserPreferences( - user._id, - settings.desktopNotifications, - 'desktopNotifications', - 'desktopPrefOrigin', - ); - } + const { desktopNotifications, pushNotifications, emailNotificationMode, highlights, language } = settings; + const promises = []; + + if (desktopNotifications) { + promises.push( + updateNotificationPreferences(user._id, 'desktopNotifications', desktopNotifications, oldDesktopNotifications, 'desktopPrefOrigin'), + ); } - if (settings.pushNotifications && oldMobileNotifications !== settings.pushNotifications) { - if (settings.pushNotifications === 'default') { - await Subscriptions.clearNotificationUserPreferences(user._id, 'mobilePushNotifications', 'mobilePrefOrigin'); - } else { - await Subscriptions.updateNotificationUserPreferences( - user._id, - settings.pushNotifications, - 'mobilePushNotifications', - 'mobilePrefOrigin', - ); - } + if (pushNotifications) { + promises.push( + updateNotificationPreferences(user._id, 'mobilePushNotifications', pushNotifications, oldMobileNotifications, 'mobilePrefOrigin'), + ); } - if (settings.emailNotificationMode && oldEmailNotifications !== settings.emailNotificationMode) { - if (settings.emailNotificationMode === 'default') { - await Subscriptions.clearNotificationUserPreferences(user._id, 'emailNotifications', 'emailPrefOrigin'); - } else { - await Subscriptions.updateNotificationUserPreferences( - user._id, - settings.emailNotificationMode, - 'emailNotifications', - 'emailPrefOrigin', - ); - } + if (emailNotificationMode) { + promises.push( + updateNotificationPreferences(user._id, 'emailNotifications', emailNotificationMode, oldEmailNotifications, 'emailPrefOrigin'), + ); } - if (Array.isArray(settings.highlights)) { - await Subscriptions.updateUserHighlights(user._id, settings.highlights); + await Promise.allSettled(promises); + + if (Array.isArray(highlights)) { + const response = await Subscriptions.updateUserHighlights(user._id, highlights); + if (response.modifiedCount) { + void notifyOnSubscriptionChangedByUserId(user._id); + } } - if (settings.language && oldLanguage !== settings.language && rcSettings.get('AutoTranslate_AutoEnableOnJoinRoom')) { - await Subscriptions.updateAllAutoTranslateLanguagesByUserId(user._id, settings.language); + if (language && oldLanguage !== language && rcSettings.get('AutoTranslate_AutoEnableOnJoinRoom')) { + const response = await Subscriptions.updateAllAutoTranslateLanguagesByUserId(user._id, language); + if (response.modifiedCount) { + void notifyOnSubscriptionChangedByAutoTranslateAndUserId(user._id); + } } }); }; diff --git a/apps/meteor/server/methods/toggleFavorite.ts b/apps/meteor/server/methods/toggleFavorite.ts index 14a363a3304a..6f3ba5f7e1a2 100644 --- a/apps/meteor/server/methods/toggleFavorite.ts +++ b/apps/meteor/server/methods/toggleFavorite.ts @@ -4,6 +4,8 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; + declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { @@ -28,6 +30,12 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-subscription', 'You must be part of a room to favorite it', { method: 'toggleFavorite' }); } - return (await Subscriptions.setFavoriteByRoomIdAndUserId(rid, userId, f)).modifiedCount; + const { modifiedCount } = await Subscriptions.setFavoriteByRoomIdAndUserId(rid, userId, f); + + if (modifiedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(userId, rid); + } + + return modifiedCount; }, }); diff --git a/apps/meteor/server/models/raw/Roles.ts b/apps/meteor/server/models/raw/Roles.ts index 84a5b088ea30..8c274a56497e 100644 --- a/apps/meteor/server/models/raw/Roles.ts +++ b/apps/meteor/server/models/raw/Roles.ts @@ -3,6 +3,7 @@ import type { IRolesModel } from '@rocket.chat/model-typings'; import { Subscriptions, Users } from '@rocket.chat/models'; import type { Collection, FindCursor, Db, Filter, FindOptions, Document } from 'mongodb'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../../app/lib/server/lib/notifyListener'; import { BaseRaw } from './BaseRaw'; export class RolesRaw extends BaseRaw implements IRolesModel { @@ -35,14 +36,12 @@ export class RolesRaw extends BaseRaw implements IRolesModel { process.env.NODE_ENV === 'development' && console.warn(`[WARN] RolesRaw.addUserRoles: role: ${roleId} not found`); continue; } - switch (role.scope) { - case 'Subscriptions': - // TODO remove dependency from other models - this logic should be inside a function/service - await Subscriptions.addRolesByUserId(userId, [role._id], scope); - break; - case 'Users': - default: - await Users.addRolesByUserId(userId, [role._id]); + + if (role.scope === 'Subscriptions' && scope) { + (await Subscriptions.addRolesByUserId(userId, [role._id], scope)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(userId, scope); + } else { + await Users.addRolesByUserId(userId, [role._id]); } } return true; @@ -88,13 +87,11 @@ export class RolesRaw extends BaseRaw implements IRolesModel { continue; } - switch (role.scope) { - case 'Subscriptions': - scope && (await Subscriptions.removeRolesByUserId(userId, [roleId], scope)); - break; - case 'Users': - default: - await Users.removeRolesByUserId(userId, [roleId]); + if (role.scope === 'Subscriptions' && scope) { + (await Subscriptions.removeRolesByUserId(userId, [roleId], scope)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(userId, scope); + } else { + await Users.removeRolesByUserId(userId, [roleId]); } } return true; diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts index e2570f60e4a4..4f70cfa89766 100644 --- a/apps/meteor/server/models/raw/Subscriptions.ts +++ b/apps/meteor/server/models/raw/Subscriptions.ts @@ -326,20 +326,35 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.find(query, options || {}); } - async removeByRoomId(roomId: string): Promise { + async removeByRoomId(roomId: ISubscription['rid']): Promise { const query = { rid: roomId, }; - const result = (await this.deleteMany(query)).deletedCount; + const deleteResult = await this.deleteMany(query); - if (typeof result === 'number' && result > 0) { - await Rooms.incUsersCountByIds([roomId], -result); + if (deleteResult?.deletedCount) { + await Rooms.incUsersCountByIds([roomId], -deleteResult.deletedCount); } await Users.removeRoomByRoomId(roomId); - return result; + return deleteResult; + } + + findByRoomIdExcludingUserIds( + roomId: ISubscription['rid'], + userIds: ISubscription['u']['_id'][], + options: FindOptions = {}, + ): FindCursor { + const query = { + 'rid': roomId, + 'u._id': { + $nin: userIds, + }, + }; + + return this.find(query, options); } async findConnectedUsersExcept( @@ -531,11 +546,10 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateMany(query, update); } - async setGroupE2EKey(_id: string, key: string): Promise { + async setGroupE2EKey(_id: string, key: string): Promise { const query = { _id }; const update = { $set: { E2EKey: key } }; - await this.updateOne(query, update); - return this.findOneById(_id); + return this.updateOne(query, update); } setGroupE2ESuggestedKey(_id: string, key: string): Promise { @@ -557,18 +571,12 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateOne({ rid }, { $unset: { onHold: 1 } }); } - findByRoomIds(roomIds: string[]): FindCursor { + findByRoomIds(roomIds: ISubscription['u']['_id'][], options?: FindOptions): FindCursor { const query = { rid: { $in: roomIds, }, }; - const options = { - projection: { - 'u._id': 1, - 'rid': 1, - }, - }; return this.find(query, options); } @@ -581,6 +589,14 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.deleteMany(query); } + findByToken(token: string, options?: FindOptions): FindCursor { + const query = { + 'v.token': token, + }; + + return this.find(query, options); + } + updateAutoTranslateById(_id: string, autoTranslate: boolean): Promise { const query = { _id, @@ -619,6 +635,19 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateMany(query, update); } + findByAutoTranslateAndUserId( + userId: ISubscription['u']['_id'], + autoTranslate: ISubscription['autoTranslate'] = true, + options?: FindOptions, + ): FindCursor { + const query = { + 'u._id': userId, + autoTranslate, + }; + + return this.find(query, options); + } + disableAutoTranslateByRoomId(roomId: IRoom['_id']): Promise { const query = { rid: roomId, @@ -1035,7 +1064,11 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return subscription?.ls; } - findByRoomIdAndUserIds(roomId: string, userIds: string[], options?: FindOptions): FindCursor { + findByRoomIdAndUserIds( + roomId: ISubscription['rid'], + userIds: ISubscription['u']['_id'][], + options?: FindOptions, + ): FindCursor { const query = { 'rid': roomId, 'u._id': { @@ -1173,6 +1206,32 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateMany(query, update); } + findByUserIdAndRoomType( + userId: ISubscription['u']['_id'], + type: ISubscription['t'], + options?: FindOptions, + ): FindCursor { + const query = { + 'u._id': userId, + 't': type, + }; + + return this.find(query, options); + } + + findByNameAndRoomType( + name?: ISubscription['name'], + fname?: ISubscription['fname'], + type?: ISubscription['t'], + options?: FindOptions, + ): FindCursor { + const query: Filter = {}; + if (name) query.name = name; + if (fname) query.fname = fname; + if (type) query.t = type; + return this.find(query, options); + } + setFavoriteByRoomIdAndUserId(roomId: string, userId: string, favorite?: boolean): Promise { if (favorite == null) { favorite = true; @@ -1354,7 +1413,7 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateOne(query, update); } - setAlertForRoomIdAndUserIds(roomId: string, uids: string[]): Promise { + setAlertForRoomIdAndUserIds(roomId: ISubscription['rid'], uids: ISubscription['u']['_id'][]): Promise { const query = { 'rid': roomId, 'u._id': { $in: uids }, @@ -1366,6 +1425,7 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri alert: true, }, }; + return this.updateMany(query, update); } @@ -1564,6 +1624,22 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateMany(query, update); } + findByUserPreferences( + userId: string, + notificationOriginField: keyof ISubscription, + notificationOriginValue: 'user' | 'subscription', + options?: FindOptions, + ): FindCursor { + const value = notificationOriginValue === 'user' ? 'user' : { $ne: 'subscription' }; + + const query: Filter = { + 'u._id': userId, + [notificationOriginField]: value, + }; + + return this.find(query, options); + } + updateUserHighlights(userId: string, userHighlights: any): Promise { const query: Filter = { 'u._id': userId, @@ -1665,9 +1741,7 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri })); // @ts-expect-error - types not good :( - const result = await this.insertMany(subscriptions); - - return result; + return this.insertMany(subscriptions); } // REMOVE @@ -1793,6 +1867,19 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.updateMany(query, update); } + findUnreadThreadsByRoomId( + rid: ISubscription['rid'], + tunread: ISubscription['tunread'], + options?: FindOptions, + ): FindCursor { + const query = { + rid, + tunread: { $in: tunread }, + }; + + return this.find(query, options); + } + openByRoomIdAndUserId(roomId: string, userId: string): Promise { const query = { 'rid': roomId, diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts index 91f5a6e66b43..ce9fb9992cd7 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts @@ -7,6 +7,7 @@ import { saveRoomTopic } from '../../../../../../app/channel-settings/server'; import { addUserToRoom } from '../../../../../../app/lib/server/functions/addUserToRoom'; import { createRoom } from '../../../../../../app/lib/server/functions/createRoom'; import { removeUserFromRoom } from '../../../../../../app/lib/server/functions/removeUserFromRoom'; +import { notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomId } from '../../../../../../app/lib/server/lib/notifyListener'; import { settings } from '../../../../../../app/settings/server'; import { getDefaultSubscriptionPref } from '../../../../../../app/utils/lib/getDefaultSubscriptionPref'; import { getValidRoomName } from '../../../../../../app/utils/server/lib/getValidRoomName'; @@ -78,9 +79,16 @@ export class RocketChatRoomAdapter { public async removeDirectMessageRoom(federatedRoom: FederatedRoom): Promise { const roomId = federatedRoom.getInternalId(); - await Rooms.removeById(roomId); - await Subscriptions.removeByRoomId(roomId); - await MatrixBridgedRoom.removeByLocalRoomId(roomId); + + const responses = await Promise.all([ + Rooms.removeById(roomId), + Subscriptions.removeByRoomId(roomId), + MatrixBridgedRoom.removeByLocalRoomId(roomId), + ]); + + if (responses[0].deletedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId, 'removed'); + } } public async createFederatedRoomForDirectMessage(federatedRoom: DirectMessageFederatedRoom): Promise { @@ -160,10 +168,13 @@ export class RocketChatRoomAdapter { } const user = federatedUser.getInternalReference(); - return Subscriptions.createWithRoomAndUser(room, user, { + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, { ts: new Date(), ...getDefaultSubscriptionPref(user), }); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } }) .filter(Boolean), ); @@ -182,33 +193,37 @@ export class RocketChatRoomAdapter { } public async updateRoomType(federatedRoom: FederatedRoom): Promise { - await Rooms.setRoomTypeById(federatedRoom.getInternalId(), federatedRoom.getRoomType()); - await Subscriptions.updateAllRoomTypesByRoomId(federatedRoom.getRoomType(), federatedRoom.getRoomType()); + const rid = federatedRoom.getInternalId(); + const roomType = federatedRoom.getRoomType(); + + await Rooms.setRoomTypeById(rid, roomType); + await Subscriptions.updateAllRoomTypesByRoomId(rid, roomType); + + void notifyOnSubscriptionChangedByRoomId(rid); } public async updateDisplayRoomName(federatedRoom: FederatedRoom, federatedUser: FederatedUser): Promise { - await Rooms.setFnameById(federatedRoom.getInternalId(), federatedRoom.getDisplayName()); - await Subscriptions.updateNameAndFnameByRoomId( - federatedRoom.getInternalId(), - federatedRoom.getName() || '', - federatedRoom.getDisplayName() || '', - ); + const rid = federatedRoom.getInternalId(); + const roomName = federatedRoom.getName() || ''; + const displayName = federatedRoom.getDisplayName() || ''; + const internalReference = federatedUser.getInternalReference(); - await Message.saveSystemMessage( - 'r', - federatedRoom.getInternalId(), - federatedRoom.getDisplayName() || '', - federatedUser.getInternalReference() as unknown as Required, // TODO fix type - ); + await Rooms.setFnameById(rid, displayName); + await Subscriptions.updateNameAndFnameByRoomId(rid, roomName, displayName); + await Message.saveSystemMessage('r', rid, displayName, internalReference); + + void notifyOnSubscriptionChangedByRoomId(rid); } public async updateRoomName(federatedRoom: FederatedRoom): Promise { - await Rooms.setRoomNameById(federatedRoom.getInternalId(), federatedRoom.getName()); - await Subscriptions.updateNameAndFnameByRoomId( - federatedRoom.getInternalId(), - federatedRoom.getName() || '', - federatedRoom.getDisplayName() || '', - ); + const rid = federatedRoom.getInternalId(); + const roomName = federatedRoom.getName() || ''; + const displayName = federatedRoom.getDisplayName() || ''; + + await Rooms.setRoomNameById(rid, roomName); + await Subscriptions.updateNameAndFnameByRoomId(rid, roomName, displayName); + + void notifyOnSubscriptionChangedByRoomId(rid); } public async updateRoomTopic(federatedRoom: FederatedRoom, federatedUser: FederatedUser): Promise { @@ -262,12 +277,15 @@ export class RocketChatRoomAdapter { rolesToRemove: ROCKET_CHAT_FEDERATION_ROLES[]; notifyChannel: boolean; }): Promise { - const subscription = await Subscriptions.findOneByRoomIdAndUserId(federatedRoom.getInternalId(), targetFederatedUser.getInternalId(), { - projection: { roles: 1 }, - }); + const uid = targetFederatedUser.getInternalId(); + const rid = federatedRoom.getInternalId(); + + const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, uid, { projection: { roles: 1 } }); + if (!subscription) { return; } + const { roles: currentRoles = [] } = subscription; const toAdd = rolesToAdd.filter((role) => !currentRoles.includes(role)); const toRemove = rolesToRemove.filter((role) => currentRoles.includes(role)); @@ -275,14 +293,16 @@ export class RocketChatRoomAdapter { _id: fromUser.getInternalId(), username: fromUser.getUsername(), }; + if (toAdd.length > 0) { - await Subscriptions.addRolesByUserId(targetFederatedUser.getInternalId(), toAdd, federatedRoom.getInternalId()); + (await Subscriptions.addRolesByUserId(uid, toAdd, rid)).modifiedCount && void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + if (notifyChannel) { await Promise.all( toAdd.map((role) => Message.saveSystemMessage( 'subscription-role-added', - federatedRoom.getInternalId(), + rid, targetFederatedUser.getInternalReference().username || '', whoDidTheChange, { role }, @@ -291,14 +311,17 @@ export class RocketChatRoomAdapter { ); } } + if (toRemove.length > 0) { - await Subscriptions.removeRolesByUserId(targetFederatedUser.getInternalId(), toRemove, federatedRoom.getInternalId()); + (await Subscriptions.removeRolesByUserId(uid, toRemove, rid)).modifiedCount && + void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + if (notifyChannel) { await Promise.all( toRemove.map((role) => Message.saveSystemMessage( 'subscription-role-removed', - federatedRoom.getInternalId(), + rid, targetFederatedUser.getInternalReference().username || '', whoDidTheChange, { role }, @@ -307,6 +330,7 @@ export class RocketChatRoomAdapter { ); } } + if (settings.get('UI_DisplayRoles')) { this.notifyUIAboutRoomRolesChange(targetFederatedUser, federatedRoom, toAdd, toRemove); } diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index aa972a846182..245c3ac7a9e5 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -72,7 +72,13 @@ export interface ISubscriptionsModel extends IBaseModel { findByUserIdAndTypes(userId: string, types: ISubscription['t'][], options?: FindOptions): FindCursor; - removeByRoomId(roomId: string): Promise; + removeByRoomId(roomId: ISubscription['rid']): Promise; + + findByRoomIdExcludingUserIds( + roomId: ISubscription['rid'], + userIds: ISubscription['u']['_id'][], + options?: FindOptions, + ): FindCursor; findConnectedUsersExcept( userId: string, @@ -94,7 +100,7 @@ export interface ISubscriptionsModel extends IBaseModel { updateNameAndFnameByRoomId(roomId: string, name: string, fname: string): Promise; - setGroupE2EKey(_id: string, key: string): Promise; + setGroupE2EKey(_id: string, key: string): Promise; setGroupE2ESuggestedKey(_id: string, key: string): Promise; @@ -118,9 +124,10 @@ export interface ISubscriptionsModel extends IBaseModel { updateAutoTranslateLanguageById(_id: string, autoTranslateLanguage: string): Promise; removeByVisitorToken(token: string): Promise; + findByToken(token: string, options?: FindOptions): FindCursor; updateMuteGroupMentions(_id: string, muteGroupMentions: boolean): Promise; - findByRoomIds(roomIds: string[]): FindCursor; + findByRoomIds(roomIds: ISubscription['u']['_id'][], options?: FindOptions): FindCursor; changeDepartmentByRoomId(rid: string, department: string): Promise; roleBaseQuery(userId: string, scope?: string): Filter | void; @@ -131,7 +138,26 @@ export interface ISubscriptionsModel extends IBaseModel { findByUserId(userId: string, options?: FindOptions): FindCursor; cachedFindByUserId(userId: string, options?: FindOptions): FindCursor; updateAutoTranslateById(_id: string, autoTranslate: boolean): Promise; + updateAllAutoTranslateLanguagesByUserId(userId: IUser['_id'], language: string): Promise; + findByAutoTranslateAndUserId( + userId: ISubscription['u']['_id'], + autoTranslate?: ISubscription['autoTranslate'], + options?: FindOptions, + ): FindCursor; + + findByUserIdAndRoomType( + userId: ISubscription['u']['_id'], + type: ISubscription['t'], + options?: FindOptions, + ): FindCursor; + findByNameAndRoomType( + name?: ISubscription['name'], + fname?: ISubscription['fname'], + type?: ISubscription['t'], + options?: FindOptions, + ): FindCursor; + disableAutoTranslateByRoomId(roomId: IRoom['_id']): Promise; findAlwaysNotifyDesktopUsersByRoomId(roomId: string): FindCursor; @@ -160,7 +186,11 @@ export interface ISubscriptionsModel extends IBaseModel { options?: FindOptions, ): FindCursor; findByRoomIdAndRoles(roomId: string, roles: string[], options?: FindOptions): FindCursor; - findByRoomIdAndUserIds(roomId: string, userIds: string[], options?: FindOptions): FindCursor; + findByRoomIdAndUserIds( + roomId: ISubscription['rid'], + userIds: ISubscription['u']['_id'][], + options?: FindOptions, + ): FindCursor; findByUserIdUpdatedAfter(userId: string, updatedAt: Date, options?: FindOptions): FindCursor; findByRoomIdAndUserIdsOrAllMessages(roomId: string, userIds: string[]): FindCursor; @@ -197,7 +227,7 @@ export interface ISubscriptionsModel extends IBaseModel { updateCustomFieldsByRoomId(rid: string, cfields: Record): Promise; setOpenForRoomIdAndUserIds(roomId: string, uids: string[]): Promise; - setAlertForRoomIdAndUserIds(roomId: string, uids: string[]): Promise; + setAlertForRoomIdAndUserIds(roomId: ISubscription['rid'], uids: ISubscription['u']['_id'][]): Promise; updateTypeByRoomId(roomId: string, type: ISubscription['t']): Promise; setBlockedByRoomId(rid: string, blocked: string, blocker: string): Promise; incUserMentionsAndUnreadForRoomIdAndUserIds( @@ -221,6 +251,12 @@ export interface ISubscriptionsModel extends IBaseModel { notificationField: keyof ISubscription, notificationOriginField: keyof ISubscription, ): Promise; + findByUserPreferences( + userId: string, + notificationOriginField: keyof ISubscription, + originFieldNotEqualValue: 'user' | 'subscription', + options?: FindOptions, + ): FindCursor; clearNotificationUserPreferences( userId: string, notificationField: string, @@ -246,6 +282,12 @@ export interface ISubscriptionsModel extends IBaseModel { removeUnreadThreadByRoomIdAndUserId(rid: string, userId: string, tmid: string, clearAlert?: boolean): Promise; removeUnreadThreadsByRoomId(rid: string, tunread: string[]): Promise; + findUnreadThreadsByRoomId( + rid: ISubscription['rid'], + tunread: ISubscription['tunread'], + options?: FindOptions, + ): FindCursor; + countByRoomIdAndRoles(roomId: string, roles: string[]): Promise; countByRoomId(roomId: string): Promise; countByUserId(userId: string): Promise; From e44c49de632a27e349fdbdfb887b3fb6c32a7b6a Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 3 Jun 2024 01:07:15 -0300 Subject: [PATCH 02/38] fix: typecheck --- .../lib/server/functions/cleanRoomHistory.ts | 6 +++--- .../app/livechat/server/lib/LivechatTyped.ts | 18 +++++++++++------- .../rocket-chat/adapters/Room.ts | 6 +++++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts index 1e1a7f0936be..98851d5e1c5a 100644 --- a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts +++ b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts @@ -84,9 +84,9 @@ export async function cleanRoomHistory({ }); if (threads.size > 0) { - const subscriptionIds = (await Subscriptions.findUnreadThreadsByRoomId(rid, [...threads], { projection: { _id: 1 } })) - .toArray() - .map(({ _id }) => _id); + const subscriptionIds: string[] = ( + await Subscriptions.findUnreadThreadsByRoomId(rid, [...threads], { projection: { _id: 1 } }).toArray() + ).map(({ _id }: { _id: string }) => _id); const removedUnreadThreadsResponse = await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index de9b57047dd0..d39db8cb4971 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -57,7 +57,11 @@ import { FileUpload } from '../../../file-upload/server'; import { deleteMessage } from '../../../lib/server/functions/deleteMessage'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; -import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; +import { + notifyOnRoomChangedById, + notifyOnSubscriptionChangedByRoomId, + notifyOnSubscriptionChangedByToken, +} from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; import { settings } from '../../../settings/server'; @@ -1269,15 +1273,15 @@ class LivechatClass { ]); } - await Promise.all([ + const responses = await Promise.all([ Subscriptions.removeByVisitorToken(token), LivechatRooms.removeByVisitorToken(token), LivechatInquiry.removeByVisitorToken(token), - ]).then((data) => { - if (data[0]?.deletedCount) { - void notifyOnSubscriptionChangedByToken(token, 'removed'); - } - }); + ]); + + if (responses[0]?.deletedCount) { + void notifyOnSubscriptionChangedByToken(token, 'removed'); + } } async deleteMessage({ guest, message }: { guest: ILivechatVisitor; message: IMessage }) { diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts index ce9fb9992cd7..c1a99eb8ee0b 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts @@ -7,7 +7,11 @@ import { saveRoomTopic } from '../../../../../../app/channel-settings/server'; import { addUserToRoom } from '../../../../../../app/lib/server/functions/addUserToRoom'; import { createRoom } from '../../../../../../app/lib/server/functions/createRoom'; import { removeUserFromRoom } from '../../../../../../app/lib/server/functions/removeUserFromRoom'; -import { notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomId } from '../../../../../../app/lib/server/lib/notifyListener'; +import { + notifyOnSubscriptionChangedById, + notifyOnSubscriptionChangedByRoomId, + notifyOnSubscriptionChangedByUserAndRoomId, +} from '../../../../../../app/lib/server/lib/notifyListener'; import { settings } from '../../../../../../app/settings/server'; import { getDefaultSubscriptionPref } from '../../../../../../app/utils/lib/getDefaultSubscriptionPref'; import { getValidRoomName } from '../../../../../../app/utils/server/lib/getValidRoomName'; From 52596d4611d516e2a894da8e5344fe85ae9bfaf1 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 3 Jun 2024 09:02:12 -0300 Subject: [PATCH 03/38] test: fix broken saveUserIdentity --- .../lib/server/functions/saveUserIdentity.ts | 9 +++++- .../app/lib/server/lib/notifyListener.ts | 30 ------------------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts index fcf30189f1cb..3258919ea700 100644 --- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts +++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts @@ -153,8 +153,15 @@ async function updateUsernameReferences({ // update other references if either the name or username has changed if (usernameChanged || nameChanged) { // update name and fname of 1-on-1 direct messages - (await Subscriptions.updateDirectNameAndFnameByName(previousUsername, rawUsername && username, rawName && name)).modifiedCount && + const updateDirectNameResponse = await Subscriptions.updateDirectNameAndFnameByName( + previousUsername, + rawUsername && username, + rawName && name, + ); + + if (updateDirectNameResponse?.modifiedCount) { void notifyOnSubscriptionChangedByNameAndRoomType(rawUsername && username, rawName && name, 'd'); + } // update name and fname of group direct messages await updateGroupDMsName(user); diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 4ab858166385..673f884963ae 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -310,41 +310,11 @@ export async function notifyOnIntegrationHistoryChangedById { -// if (!dbWatchersDisabled) { -// return; -// } - -// void api.broadcast('watch.subscriptions', { clientAction, subscription }); -// } - -// export async function notifyOnSubscriptionChangedById( -// _id: ISubscription['_id'], -// clientAction: ClientAction = 'updated', -// ): Promise { -// if (!dbWatchersDisabled) { -// return; -// } - -// const subscription = clientAction === 'removed' ? await Subscriptions.trashFindOneById(_id) : await Subscriptions.findOneById(_id); - -// if (!subscription) { -// return; -// } - -// void api.broadcast('watch.subscriptions', { clientAction, subscription }); -// } - export async function notifyOnSubscriptionChangedByUserAndRoomId( uid: ISubscription['u']['_id'], rid: ISubscription['rid'], clientAction: ClientAction = 'updated', - // diff?: Partial, ): Promise { - // || !hasSubscriptionFields(diff) if (!dbWatchersDisabled) { return; } From 7463d429719ee2d36169f6f67f1312190b852e92 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 3 Jun 2024 09:30:26 -0300 Subject: [PATCH 04/38] lint fix --- apps/meteor/app/federation/server/endpoints/dispatch.js | 6 +++++- .../app/message-mark-as-unread/server/unreadMessages.ts | 3 +-- apps/meteor/app/threads/server/functions.ts | 3 +-- .../server/services/omnichannel.internalService.ts | 5 +---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index 150849820e88..3f578f6231cb 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -7,7 +7,11 @@ import { broadcastMessageFromData } from '../../../../server/modules/watchers/li import { API } from '../../../api/server'; import { FileUpload } from '../../../file-upload/server'; import { deleteRoom } from '../../../lib/server/functions/deleteRoom'; -import { notifyOnRoomChanged, notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener'; +import { + notifyOnRoomChanged, + notifyOnRoomChangedById, + notifyOnSubscriptionChangedByUserAndRoomId, +} from '../../../lib/server/lib/notifyListener'; import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage'; import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage'; import { processThreads } from '../../../threads/server/hooks/aftersavemessage'; diff --git a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts index cbf3f488c90d..aafafc522ce3 100644 --- a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts +++ b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts @@ -3,9 +3,8 @@ import { Messages, Subscriptions } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import logger from './logger'; - import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../lib/server/lib/notifyListener'; +import logger from './logger'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index b9f120fa7d89..fe2e6a1e928d 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -2,12 +2,11 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocket.chat/models'; -import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; - import { notifyOnSubscriptionChangedByRoomIdAndUserIds, notifyOnSubscriptionChangedByUserAndRoomId, } from '../../lib/server/lib/notifyListener'; +import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { if (!tmid || isEditedMessage(message)) { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts index 8ef146d466f8..d5ea9b5d91db 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts @@ -5,10 +5,7 @@ import type { IOmnichannelRoom, IUser, ILivechatInquiryRecord, IOmnichannelSyste import { Logger } from '@rocket.chat/logger'; import { LivechatRooms, Subscriptions, LivechatInquiry } from '@rocket.chat/models'; -import { - notifyOnRoomChangedById, - notifyOnSubscriptionChangedByRoomId, -} from '../../../../../app/lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../../../../../app/lib/server/lib/notifyListener'; import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper'; import { queueInquiry } from '../../../../../app/livechat/server/lib/QueueManager'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; From 054d24af1749b1fb6264105d60430409c5e108c2 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 4 Jun 2024 21:59:22 -0300 Subject: [PATCH 05/38] refactor: adds listener calls on functions calling model's raw method directly --- .../app/federation/server/endpoints/dispatch.js | 14 ++++++++++++-- .../app/lib/server/functions/createDirectRoom.ts | 12 +++++++++--- apps/meteor/app/livechat/server/lib/Helper.ts | 8 +++++++- apps/meteor/server/services/team/service.ts | 7 ++++++- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index 3f578f6231cb..e0fcdc3ccacc 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -10,6 +10,7 @@ import { deleteRoom } from '../../../lib/server/functions/deleteRoom'; import { notifyOnRoomChanged, notifyOnRoomChangedById, + notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByUserAndRoomId, } from '../../../lib/server/lib/notifyListener'; import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage'; @@ -138,7 +139,13 @@ const eventHandlers = { if (persistedSubscription) { // Update the federation, if its not already set (if it's set, this is likely an event being reprocessed if (!persistedSubscription.federation) { - await Subscriptions.updateOne({ _id: persistedSubscription._id }, { $set: { federation: subscription.federation } }); + const { modifiedCount } = await Subscriptions.updateOne( + { _id: persistedSubscription._id }, + { $set: { federation: subscription.federation } }, + ); + if (modifiedCount) { + void notifyOnSubscriptionChangedById(persistedSubscription._id); + } federationAltered = true; } } else { @@ -146,7 +153,10 @@ const eventHandlers = { const denormalizedSubscription = normalizers.denormalizeSubscription(subscription); // Create the subscription - await Subscriptions.insertOne(denormalizedSubscription); + const { insertedId } = await Subscriptions.insertOne(denormalizedSubscription); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId); + } federationAltered = true; } } catch (ex) { diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index 67c6328f38f4..85b2fe297149 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -11,7 +11,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { isTruthy } from '../../../../lib/isTruthy'; import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByUserAndRoomId } from '../lib/notifyListener'; const generateSubscription = ( fname: string, @@ -135,7 +135,7 @@ export async function createDirectRoom( if (roomMembers.length === 1) { // dm to yourself - await Subscriptions.updateOne( + const { modifiedCount, upsertedCount } = await Subscriptions.updateOne( { rid, 'u._id': roomMembers[0]._id }, { $set: { open: true }, @@ -146,6 +146,9 @@ export async function createDirectRoom( }, { upsert: true }, ); + if (modifiedCount || upsertedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(roomMembers[0]._id, rid, modifiedCount ? 'updated' : 'inserted'); + } } else { const memberIds = roomMembers.map((member) => member._id); const membersWithPreferences: IUser[] = await Users.find( @@ -155,7 +158,7 @@ export async function createDirectRoom( for await (const member of membersWithPreferences) { const otherMembers = sortedMembers.filter(({ _id }) => _id !== member._id); - await Subscriptions.updateOne( + const { modifiedCount, upsertedCount } = await Subscriptions.updateOne( { rid, 'u._id': member._id }, { ...(options?.creator === member._id && { $set: { open: true } }), @@ -166,6 +169,9 @@ export async function createDirectRoom( }, { upsert: true }, ); + if (modifiedCount || upsertedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(member._id, rid, modifiedCount ? 'updated' : 'inserted'); + } } } diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 817e0ae38a7b..d4e66a4b8cff 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -259,7 +259,13 @@ export const createLivechatSubscription = async ( ...(department && { department }), } as InsertionModel; - return Subscriptions.insertOne(subscriptionData); + const response = await Subscriptions.insertOne(subscriptionData); + + if (response?.insertedId) { + void notifyOnSubscriptionChangedById(response.insertedId, 'inserted'); + } + + return response; }; export const removeAgentFromSubscription = async (rid: string, { _id, username }: Pick) => { diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index 71d403fd84b5..ff5e32657e85 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -32,6 +32,7 @@ import { addUserToRoom } from '../../../app/lib/server/functions/addUserToRoom'; import { checkUsernameAvailability } from '../../../app/lib/server/functions/checkUsernameAvailability'; import { getSubscribedRoomsForUserWithDetails } from '../../../app/lib/server/functions/getRoomsWithSingleOwner'; import { removeUserFromRoom } from '../../../app/lib/server/functions/removeUserFromRoom'; +import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../../app/lib/server/lib/notifyListener'; export class TeamService extends ServiceClassInternal implements ITeamService { protected name = 'team'; @@ -730,7 +731,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService { throw new Error('invalid-team'); } - await Promise.all([ + const responses = await Promise.all([ TeamMember.updateOneByUserIdAndTeamId(member.userId, teamId, memberUpdate), Subscriptions.updateOne( { @@ -742,6 +743,10 @@ export class TeamService extends ServiceClassInternal implements ITeamService { }, ), ]); + + if (responses[1].modifiedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(member.userId, team.roomId); + } } async removeMember(teamId: string, userId: string): Promise { From b6a39afad5abf734692d3dcd9ae81ce57dc9e3c2 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Tue, 4 Jun 2024 22:22:25 -0300 Subject: [PATCH 06/38] fix typecheck --- apps/meteor/app/livechat/server/lib/Helper.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index d4e66a4b8cff..34f2ebe1c38d 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -37,7 +37,11 @@ import { i18n } from '../../../../server/lib/i18n'; import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { sendNotification } from '../../../lib/server'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; -import { notifyOnSubscriptionChangedByRoomId, notifyOnSubscriptionChangedByUserAndRoomId } from '../../../lib/server/lib/notifyListener'; +import { + notifyOnSubscriptionChangedById, + notifyOnSubscriptionChangedByRoomId, + notifyOnSubscriptionChangedByUserAndRoomId, +} from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { Livechat as LivechatTyped } from './LivechatTyped'; import { queueInquiry, saveQueueInquiry } from './QueueManager'; From ed1146d22d491d0dedbb5850eb072ce759157483 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Wed, 5 Jun 2024 17:38:44 -0300 Subject: [PATCH 07/38] refactor: change function calls to use if statement - Replaced asynchronous function calls with synchronous calls followed by an if statement to check for modifiedCount before invoking the listener. --- .../server/methods/saveSettings.ts | 28 +++++++++++++++---- .../server/functions/saveRoomCustomFields.ts | 5 +++- .../server/functions/saveRoomEncrypted.ts | 5 +++- .../app/e2e/server/methods/updateGroupKey.ts | 10 +++++-- .../app/lib/server/functions/archiveRoom.ts | 5 +++- .../saveCustomFieldsWithoutValidation.ts | 4 ++- .../server/functions/setUserActiveStatus.ts | 13 +++++++-- .../app/lib/server/functions/unarchiveRoom.ts | 5 +++- .../server/functions/updateGroupDMsName.ts | 4 ++- .../server/unreadMessages.ts | 8 ++++-- .../methods/saveNotificationSettings.ts | 10 +++++-- .../server/hooks/afterSaveMessage.ts | 4 ++- apps/meteor/server/lib/readMessages.ts | 4 ++- apps/meteor/server/methods/addRoomLeader.ts | 5 +++- .../meteor/server/methods/addRoomModerator.ts | 4 ++- apps/meteor/server/methods/addRoomOwner.ts | 5 +++- .../meteor/server/methods/removeRoomLeader.ts | 4 ++- .../server/methods/removeRoomModerator.ts | 4 ++- apps/meteor/server/methods/removeRoomOwner.ts | 5 +++- .../server/methods/saveUserPreferences.ts | 8 ++++-- apps/meteor/server/models/raw/Roles.ts | 8 ++++-- .../rocket-chat/adapters/Room.ts | 9 ++++-- 22 files changed, 122 insertions(+), 35 deletions(-) diff --git a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts index a68eaa8007d9..b8f27cf2240e 100644 --- a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts +++ b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts @@ -45,6 +45,8 @@ Meteor.methods({ }); } + let shouldNotifySubscriptionChanged = false; + switch (field) { case 'autoTranslate': const room = await Rooms.findE2ERoomById(rid, { projection: { _id: 1 } }); @@ -54,20 +56,34 @@ Meteor.methods({ }); } - (await Subscriptions.updateAutoTranslateById(subscription._id, value === '1')).modifiedCount && - void notifyOnSubscriptionChangedById(subscription._id); + const updateAutoTranslateResponse = await Subscriptions.updateAutoTranslateById(subscription._id, value === '1'); + if (updateAutoTranslateResponse.modifiedCount) { + shouldNotifySubscriptionChanged = true; + } if (!subscription.autoTranslateLanguage && options.defaultLanguage) { - (await Subscriptions.updateAutoTranslateLanguageById(subscription._id, options.defaultLanguage)).modifiedCount && - void notifyOnSubscriptionChangedById(subscription._id); + const updateAutoTranslateLanguageResponse = await Subscriptions.updateAutoTranslateLanguageById( + subscription._id, + options.defaultLanguage, + ); + if (updateAutoTranslateLanguageResponse.modifiedCount) { + shouldNotifySubscriptionChanged = true; + } } + break; case 'autoTranslateLanguage': - (await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value)).modifiedCount && - void notifyOnSubscriptionChangedById(subscription._id); + const updateAutoTranslateLanguage = await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value); + if (updateAutoTranslateLanguage.modifiedCount) { + shouldNotifySubscriptionChanged = true; + } break; } + if (shouldNotifySubscriptionChanged) { + void notifyOnSubscriptionChangedById(subscription._id); + } + return true; }, }); diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts index 94fbb0100537..de2cf8139af1 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts @@ -21,7 +21,10 @@ export const saveRoomCustomFields = async function (rid: string, roomCustomField const response = await Rooms.setCustomFieldsById(rid, roomCustomFields); // Update customFields of any user's Subscription related with this rid - (await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); + const updateCustomFields = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields); + if (updateCustomFields.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } return response; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts index 24321391cc27..cb9da9be5840 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts @@ -29,7 +29,10 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean } if (encrypted) { - (await Subscriptions.disableAutoTranslateByRoomId(rid)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); + const disableAutoTranslateResponse = await Subscriptions.disableAutoTranslateByRoomId(rid); + if (disableAutoTranslateResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } } return update; }; diff --git a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts index 5a11babe8bf2..99bc538e96bd 100644 --- a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts +++ b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts @@ -24,14 +24,20 @@ Meteor.methods({ if (mySub) { // Setting the key to myself, can set directly to the final field if (userId === uid) { - (await Subscriptions.setGroupE2EKey(mySub._id, key)).modifiedCount && void notifyOnSubscriptionChangedById(mySub._id); + const setGroupE2EKeyResponse = await Subscriptions.setGroupE2EKey(mySub._id, key); + if (setGroupE2EKeyResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(mySub._id); + } return; } // uid also has subscription to this room const userSub = await Subscriptions.findOneByRoomIdAndUserId(rid, uid); if (userSub) { - (await Subscriptions.setGroupE2ESuggestedKey(userSub._id, key)).modifiedCount && void notifyOnSubscriptionChangedById(userSub._id); + const setGroupE2ESuggestedKeyResponse = await Subscriptions.setGroupE2ESuggestedKey(userSub._id, key); + if (setGroupE2ESuggestedKeyResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(userSub._id); + } } } }, diff --git a/apps/meteor/app/lib/server/functions/archiveRoom.ts b/apps/meteor/app/lib/server/functions/archiveRoom.ts index d643123fbd06..46fd7a1ac35b 100644 --- a/apps/meteor/app/lib/server/functions/archiveRoom.ts +++ b/apps/meteor/app/lib/server/functions/archiveRoom.ts @@ -8,7 +8,10 @@ import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomId } from '../lib export const archiveRoom = async function (rid: string, user: IMessage['u']): Promise { await Rooms.archiveById(rid); - (await Subscriptions.archiveByRoomId(rid)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); + const archiveResponse = await Subscriptions.archiveByRoomId(rid); + if (archiveResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } await Message.saveSystemMessage('room-archived', rid, '', user); diff --git a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts index 8b513cd4382a..5383048f13bd 100644 --- a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts +++ b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts @@ -23,8 +23,10 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string, await Users.setCustomFields(userId, customFields); // Update customFields of all Direct Messages' Rooms for userId - (await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields)).modifiedCount && + const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields); + if (setCustomFieldsResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd'); + } for await (const fieldName of Object.keys(customFields)) { if (!customFieldsMeta[fieldName].modifyRecordField) { diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts index 170bf99f328e..924ba31e790a 100644 --- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts +++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts @@ -42,8 +42,10 @@ async function reactivateDirectConversations(userId: string) { return acc; }, []); - (await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false)).modifiedCount && + const setDmReadOnlyResponse = await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false); + if (setDmReadOnlyResponse.modifiedCount) { void notifyOnRoomChangedById(roomsToReactivate); + } } export async function setUserActiveStatus(userId: string, active: boolean, confirmRelinquish = false): Promise { @@ -105,13 +107,18 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi } if (user.username) { - (await Subscriptions.setArchivedByUsername(user.username, !active)).modifiedCount && + const setArchivedResponse = await Subscriptions.setArchivedByUsername(user.username, !active); + if (setArchivedResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserIdAndRoomType(user._id, 'd'); + } } if (active === false) { await Users.unsetLoginTokens(userId); - (await Rooms.setDmReadOnlyByUserId(userId, undefined, true, false)).modifiedCount && void notifyOnRoomChangedByUserDM(userId); + const setDmReadOnlyResponse = await Rooms.setDmReadOnlyByUserId(userId, undefined, true, false); + if (setDmReadOnlyResponse.modifiedCount) { + void notifyOnRoomChangedByUserDM(userId); + } } else { await Users.unsetReason(userId); await reactivateDirectConversations(userId); diff --git a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts index 59d15ada1213..699f9c3701b1 100644 --- a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts +++ b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts @@ -7,7 +7,10 @@ import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '.. export const unarchiveRoom = async function (rid: string, user: IMessage['u']): Promise { await Rooms.unarchiveById(rid); - (await Subscriptions.unarchiveByRoomId(rid)).modifiedCount && void notifyOnSubscriptionChangedByRoomId(rid); + const unarchiveResponse = await Subscriptions.unarchiveByRoomId(rid); + if (unarchiveResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } await Message.saveSystemMessage('room-unarchived', rid, '', user); diff --git a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts index d548e5f48c0b..feb26ce6a1b0 100644 --- a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts +++ b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts @@ -65,8 +65,10 @@ export const updateGroupDMsName = async (userThatChangedName: IUser): Promise _id !== sub.u._id); - (await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers))).modifiedCount && + const updateNameRespose = await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers)); + if (updateNameRespose.modifiedCount) { void notifyOnSubscriptionChangedByRoomId(room._id); + } } } }; diff --git a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts index aafafc522ce3..098a3543ef15 100644 --- a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts +++ b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts @@ -37,8 +37,10 @@ Meteor.methods({ }); } - (await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts)).modifiedCount && + const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts); + if (setAsUnreadResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(userId, lastMessage.rid); + } return; } @@ -77,7 +79,9 @@ Meteor.methods({ } logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`); - (await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts)).modifiedCount && + const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts); + if (setAsUnreadResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(userId, originalMessage.rid); + } }, }); diff --git a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts index 601ecbecd561..25f7e214eb8b 100644 --- a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts +++ b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts @@ -133,7 +133,10 @@ Meteor.methods({ }); } - (await notifications[field].updateMethod(subscription, value)).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); + const updateResponse = await notifications[field].updateMethod(subscription, value); + if (updateResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } return true; }, @@ -153,7 +156,10 @@ Meteor.methods({ }); } - (await saveAudioNotificationValue(subscription._id, value)).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); + const saveAudioNotificationResponse = await saveAudioNotificationValue(subscription._id, value); + if (saveAudioNotificationResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } return true; }, diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts index d0ff679ba4cb..9511ddee9b0c 100644 --- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts +++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts @@ -16,8 +16,10 @@ callbacks.add( if (!isOmnichannelRoom(room) || !room.closedAt) { // set subscription as read right after message was sent - (await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id)).modifiedCount && + const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id); + if (setAsReadResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(message.u._id, room._id); + } } // mark message as read as well diff --git a/apps/meteor/server/lib/readMessages.ts b/apps/meteor/server/lib/readMessages.ts index 95506aa46a9e..e9ac09de0b8d 100644 --- a/apps/meteor/server/lib/readMessages.ts +++ b/apps/meteor/server/lib/readMessages.ts @@ -16,8 +16,10 @@ export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThr // do not mark room as read if there are still unread threads const alert = !!(sub.alert && !readThreads && sub.tunread && sub.tunread.length > 0); - (await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert)).modifiedCount && + const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert); + if (setAsReadResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + } await NotificationQueue.clearQueueByUserId(uid); diff --git a/apps/meteor/server/methods/addRoomLeader.ts b/apps/meteor/server/methods/addRoomLeader.ts index 520854a3a65c..bdb863c35c23 100644 --- a/apps/meteor/server/methods/addRoomLeader.ts +++ b/apps/meteor/server/methods/addRoomLeader.ts @@ -57,7 +57,10 @@ Meteor.methods({ }); } - (await Subscriptions.addRoleById(subscription._id, 'leader')).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); + const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'leader'); + if (addRoleResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } const fromUser = await Users.findOneById(uid); diff --git a/apps/meteor/server/methods/addRoomModerator.ts b/apps/meteor/server/methods/addRoomModerator.ts index db404af3cf41..8ed06bceb0ce 100644 --- a/apps/meteor/server/methods/addRoomModerator.ts +++ b/apps/meteor/server/methods/addRoomModerator.ts @@ -65,8 +65,10 @@ Meteor.methods({ }); } - (await Subscriptions.addRoleById(subscription._id, 'moderator')).modifiedCount && + const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'moderator'); + if (addRoleResponse.modifiedCount) { void notifyOnSubscriptionChangedById(subscription._id); + } const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/addRoomOwner.ts b/apps/meteor/server/methods/addRoomOwner.ts index 0baf316d12e9..10af34e6fc0f 100644 --- a/apps/meteor/server/methods/addRoomOwner.ts +++ b/apps/meteor/server/methods/addRoomOwner.ts @@ -65,7 +65,10 @@ Meteor.methods({ }); } - (await Subscriptions.addRoleById(subscription._id, 'owner')).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); + const addRoleResponse = await Subscriptions.addRoleById(subscription._id, 'owner'); + if (addRoleResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/removeRoomLeader.ts b/apps/meteor/server/methods/removeRoomLeader.ts index 19233e1654af..b88ce75fc9e3 100644 --- a/apps/meteor/server/methods/removeRoomLeader.ts +++ b/apps/meteor/server/methods/removeRoomLeader.ts @@ -57,8 +57,10 @@ Meteor.methods({ }); } - (await Subscriptions.removeRoleById(subscription._id, 'leader')).modifiedCount && + const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'leader'); + if (removeRoleResponse.modifiedCount) { void notifyOnSubscriptionChangedById(subscription._id); + } const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/removeRoomModerator.ts b/apps/meteor/server/methods/removeRoomModerator.ts index 89ab0debbc10..7fa9d6c2d498 100644 --- a/apps/meteor/server/methods/removeRoomModerator.ts +++ b/apps/meteor/server/methods/removeRoomModerator.ts @@ -65,8 +65,10 @@ Meteor.methods({ }); } - (await Subscriptions.removeRoleById(subscription._id, 'moderator')).modifiedCount && + const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'moderator'); + if (removeRoleResponse.modifiedCount) { void notifyOnSubscriptionChangedById(subscription._id); + } const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/removeRoomOwner.ts b/apps/meteor/server/methods/removeRoomOwner.ts index da2607933272..57c1f136e8c1 100644 --- a/apps/meteor/server/methods/removeRoomOwner.ts +++ b/apps/meteor/server/methods/removeRoomOwner.ts @@ -72,7 +72,10 @@ Meteor.methods({ }); } - (await Subscriptions.removeRoleById(subscription._id, 'owner')).modifiedCount && void notifyOnSubscriptionChangedById(subscription._id); + const removeRoleResponse = await Subscriptions.removeRoleById(subscription._id, 'owner'); + if (removeRoleResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } const fromUser = await Users.findOneById(uid); if (!fromUser) { diff --git a/apps/meteor/server/methods/saveUserPreferences.ts b/apps/meteor/server/methods/saveUserPreferences.ts index 5bd1ebe63899..55384faea6e8 100644 --- a/apps/meteor/server/methods/saveUserPreferences.ts +++ b/apps/meteor/server/methods/saveUserPreferences.ts @@ -71,13 +71,17 @@ async function updateNotificationPreferences( } if (newValue === 'default') { - (await Subscriptions.clearNotificationUserPreferences(userId, setting, preferenceType)).modifiedCount && + const clearNotificationResponse = await Subscriptions.clearNotificationUserPreferences(userId, setting, preferenceType); + if (clearNotificationResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserPreferences(userId, preferenceType, 'user'); + } return; } - (await Subscriptions.updateNotificationUserPreferences(userId, newValue, setting, preferenceType)).modifiedCount && + const updateNotificationResponse = await Subscriptions.updateNotificationUserPreferences(userId, newValue, setting, preferenceType); + if (updateNotificationResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserPreferences(userId, preferenceType, 'subscription'); + } } export const saveUserPreferences = async (settings: Partial, userId: string): Promise => { diff --git a/apps/meteor/server/models/raw/Roles.ts b/apps/meteor/server/models/raw/Roles.ts index 8c274a56497e..f62bb9cf8356 100644 --- a/apps/meteor/server/models/raw/Roles.ts +++ b/apps/meteor/server/models/raw/Roles.ts @@ -38,8 +38,10 @@ export class RolesRaw extends BaseRaw implements IRolesModel { } if (role.scope === 'Subscriptions' && scope) { - (await Subscriptions.addRolesByUserId(userId, [role._id], scope)).modifiedCount && + const addRolesResponse = await Subscriptions.addRolesByUserId(userId, [role._id], scope); + if (addRolesResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(userId, scope); + } } else { await Users.addRolesByUserId(userId, [role._id]); } @@ -88,8 +90,10 @@ export class RolesRaw extends BaseRaw implements IRolesModel { } if (role.scope === 'Subscriptions' && scope) { - (await Subscriptions.removeRolesByUserId(userId, [roleId], scope)).modifiedCount && + const removeRolesResponse = await Subscriptions.removeRolesByUserId(userId, [roleId], scope); + if (removeRolesResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(userId, scope); + } } else { await Users.removeRolesByUserId(userId, [roleId]); } diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts index c1a99eb8ee0b..c783e175f1ce 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts @@ -299,7 +299,10 @@ export class RocketChatRoomAdapter { }; if (toAdd.length > 0) { - (await Subscriptions.addRolesByUserId(uid, toAdd, rid)).modifiedCount && void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + const addRolesResponse = await Subscriptions.addRolesByUserId(uid, toAdd, rid); + if (addRolesResponse.modifiedCount) { + void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + } if (notifyChannel) { await Promise.all( @@ -317,8 +320,10 @@ export class RocketChatRoomAdapter { } if (toRemove.length > 0) { - (await Subscriptions.removeRolesByUserId(uid, toRemove, rid)).modifiedCount && + const removeRolesResponse = await Subscriptions.removeRolesByUserId(uid, toRemove, rid); + if (removeRolesResponse.modifiedCount) { void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + } if (notifyChannel) { await Promise.all( From 308934f5f9ffda02fc89f13b5f2f0cb173e37ae4 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Fri, 21 Jun 2024 14:36:06 -0300 Subject: [PATCH 08/38] lint and typecheck fixes --- .../importer/server/classes/ImportDataConverter.ts | 10 +++++----- apps/meteor/app/lib/server/functions/deleteUser.ts | 1 + .../app/livechat/server/lib/LivechatTyped.ts | 14 +++++--------- apps/meteor/server/services/team/service.ts | 8 ++++---- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts index c5847cbe14b7..6de47e33b2b6 100644 --- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts @@ -361,11 +361,11 @@ export class ImportDataConverter { ...user.services, ...(this._options.enableEmail2fa ? { - email2fa: { - enabled: true, - changedAt: new Date(), - }, - } + email2fa: { + enabled: true, + changedAt: new Date(), + }, + } : {}), }, } as IUser; diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index ec1917fa8aa3..71aac8c777fa 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -24,6 +24,7 @@ import { notifyOnIntegrationChangedByUserId, notifyOnLivechatDepartmentAgentChanged, notifyOnUserChange, + notifyOnSubscriptionChangedByUserId, } from '../lib/notifyListener'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 3b4b1045f86c..4f1bc6306d05 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -528,7 +528,7 @@ class LivechatClass { void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); } - if (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount) { + if (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount && inquiry) { void notifyOnLivechatInquiryChanged(inquiry, 'removed'); } @@ -1294,19 +1294,15 @@ class LivechatClass { ]); } - const responses = await Promise.all([ - Subscriptions.removeByVisitorToken(token), - LivechatRooms.removeByVisitorToken(token), - LivechatInquiry.removeByVisitorToken(token), - ]); + const responses = await Promise.all([Subscriptions.removeByVisitorToken(token), LivechatRooms.removeByVisitorToken(token)]); if (responses[0]?.deletedCount) { void notifyOnSubscriptionChangedByToken(token, 'removed'); } - if (response[2]?.deletedCount) { - void notifyOnLivechatInquiryChanged(livechatInquiries, 'removed'); - } + const livechatInquiries = await LivechatInquiry.findIdsByVisitorToken(token).toArray(); + await LivechatInquiry.removeByIds(livechatInquiries.map(({ _id }) => _id)); + void notifyOnLivechatInquiryChanged(livechatInquiries, 'removed'); } async deleteMessage({ guest, message }: { guest: ILivechatVisitor; message: IMessage }) { diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index 72e6530ca280..84a1d9d5aa98 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -61,8 +61,8 @@ export class TeamService extends ServiceClassInternal implements ITeamService { !members || !Array.isArray(members) || members.length === 0 ? [] : await Users.findActiveByIdsOrUsernames(members, { - projection: { username: 1 }, - }).toArray(); + projection: { username: 1 }, + }).toArray(); const memberUsernames = membersResult.map(({ username }) => username); const memberIds = membersResult.map(({ _id }) => _id); @@ -809,8 +809,8 @@ export class TeamService extends ServiceClassInternal implements ITeamService { removedUser, uid !== member.userId ? { - byUser, - } + byUser, + } : undefined, ); } From 091aea3aa4b28bac1c91e5583612e7c26c2b95a9 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 21 Jun 2024 17:13:31 -0300 Subject: [PATCH 09/38] remove collection --- apps/meteor/server/database/watchCollections.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/server/database/watchCollections.ts b/apps/meteor/server/database/watchCollections.ts index 3c030526f00f..6dd173d5d323 100644 --- a/apps/meteor/server/database/watchCollections.ts +++ b/apps/meteor/server/database/watchCollections.ts @@ -29,7 +29,7 @@ const onlyCollections = DBWATCHER_ONLY_COLLECTIONS.split(',') .filter(Boolean); export function getWatchCollections(): string[] { - const collections = [InstanceStatus.getCollectionName(), Subscriptions.getCollectionName()]; + const collections = [InstanceStatus.getCollectionName()]; // add back to the list of collections in case db watchers are enabled if (!dbWatchersDisabled) { From 818e8ef7604255197af998e88cb63df205b37030 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 1 Jul 2024 09:27:52 -0300 Subject: [PATCH 10/38] refactor: change handleSuggestedGroupKey to initial behavior --- .../functions/handleSuggestedGroupKey.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts index 0d1e55a2ad09..13163b318aab 100644 --- a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts +++ b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts @@ -1,4 +1,4 @@ -import { Subscriptions } from '@rocket.chat/models'; +import { Subscriptions, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; @@ -23,12 +23,16 @@ export async function handleSuggestedGroupKey( throw new Meteor.Error('error-no-suggested-key-available', 'No suggested key available', { method }); } - const e2eKeyUpdateResult = - handle === 'accept' - ? await Subscriptions.setGroupE2EKey(sub._id, suggestedKey) - : await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); + if (handle === 'accept') { + await Subscriptions.setGroupE2EKey(sub._id, suggestedKey); + await Rooms.removeUsersFromE2EEQueueByRoomId(sub.rid, [userId]); + } - if (e2eKeyUpdateResult?.modifiedCount) { - void notifyOnSubscriptionChangedById(sub._id); + if (handle === 'reject') { + await Rooms.addUserIdToE2EEQueueByRoomIds([sub.rid], userId); } + + await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); + + void notifyOnSubscriptionChangedById(sub._id); } From 3b0c0b7e344e17ced01f3c97e38ff6670426ae44 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 1 Jul 2024 09:35:50 -0300 Subject: [PATCH 11/38] refactor: remove listener call on ROOM_ADD_USER when updating federation subscription --- apps/meteor/app/federation/server/endpoints/dispatch.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index e0fcdc3ccacc..9318007253b7 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -139,13 +139,7 @@ const eventHandlers = { if (persistedSubscription) { // Update the federation, if its not already set (if it's set, this is likely an event being reprocessed if (!persistedSubscription.federation) { - const { modifiedCount } = await Subscriptions.updateOne( - { _id: persistedSubscription._id }, - { $set: { federation: subscription.federation } }, - ); - if (modifiedCount) { - void notifyOnSubscriptionChangedById(persistedSubscription._id); - } + await Subscriptions.updateOne({ _id: persistedSubscription._id }, { $set: { federation: subscription.federation } }); federationAltered = true; } } else { From dfa67128a60ee2f12397e6b9125705b625ade630 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 1 Jul 2024 10:19:38 -0300 Subject: [PATCH 12/38] refactor: validate returned deletedCount before calling listener --- apps/meteor/app/lib/server/functions/deleteUser.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index 71aac8c777fa..02b0b82b16ae 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -99,9 +99,10 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele const rids = subscribedRooms.map((room) => room.rid); void notifyOnRoomChangedById(rids); - // TODO: handle modifiedCount and/or return validity to call listener - await Subscriptions.removeByUserId(userId); - void notifyOnSubscriptionChangedByUserId(userId); + const deletedCount = await Subscriptions.removeByUserId(userId); + if (deletedCount) { + void notifyOnSubscriptionChangedByUserId(userId); + } // Remove user as livechat agent if (user.roles.includes('livechat-agent')) { From f3a36952eb1a30fbf7c76ce0bdb9d795187f280c Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 1 Jul 2024 10:22:30 -0300 Subject: [PATCH 13/38] refactor: changes then to await promises --- apps/meteor/app/livechat/server/lib/Helper.ts | 12 ++++++------ .../server/hooks/onCloseLivechat.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index d0edf11d0a9e..27311fa46b8e 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -493,15 +493,15 @@ export const updateChatDepartment = async ({ newDepartmentId: string; oldDepartmentId?: string; }) => { - await Promise.all([ + const responses = await Promise.all([ LivechatRooms.changeDepartmentIdByRoomId(rid, newDepartmentId), LivechatInquiry.changeDepartmentIdByRoomId(rid, newDepartmentId), Subscriptions.changeDepartmentByRoomId(rid, newDepartmentId), - ]).then((data) => { - if (data[2].modifiedCount) { - void notifyOnSubscriptionChangedByRoomId(rid); - } - }); + ]); + + if (responses[2].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } setImmediate(() => { void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts index c851aa6033d2..bc2d4fb6a3fb 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts @@ -17,15 +17,15 @@ const onCloseLivechat = async (params: LivechatCloseCallbackParams) => { room: { _id: roomId }, } = params; - await Promise.all([ + const responses = await Promise.all([ LivechatRooms.unsetOnHoldByRoomId(roomId), Subscriptions.unsetOnHoldByRoomId(roomId), AutoCloseOnHoldScheduler.unscheduleRoom(roomId), - ]).then((data) => { - if (data[1].modifiedCount) { - void notifyOnSubscriptionChangedByRoomId(roomId); - } - }); + ]); + + if (responses[1].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId); + } if (!settings.get('Livechat_waiting_queue')) { return params; From 5f4465a4ae6f937503b340bcefe4cc1860768e13 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Wed, 3 Jul 2024 22:43:29 -0300 Subject: [PATCH 14/38] refactor: notify all existing DMs from archived user --- .../lib/server/functions/setUserActiveStatus.ts | 4 ++-- apps/meteor/app/lib/server/lib/notifyListener.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts index 26011637e06d..8e376c2f8ec6 100644 --- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts +++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts @@ -11,7 +11,7 @@ import { settings } from '../../../settings/server'; import { notifyOnRoomChangedById, notifyOnRoomChangedByUserDM, - notifyOnSubscriptionChangedByUserIdAndRoomType, + notifySubscriptionsOnUserArchived, notifyOnUserChange, } from '../lib/notifyListener'; import { closeOmnichannelConversations } from './closeOmnichannelConversations'; @@ -110,7 +110,7 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi if (user.username) { const setArchivedResponse = await Subscriptions.setArchivedByUsername(user.username, !active); if (setArchivedResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserIdAndRoomType(user._id, 'd'); + void notifySubscriptionsOnUserArchived(user._id, 'd'); } } diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index c5d5727ec180..f75e8dbf4489 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -632,6 +632,22 @@ export async function notifyOnSubscriptionChangedByAutoTranslateAndUserId( } } +export async function notifySubscriptionsOnUserArchived( + uid: ISubscription['u']['_id'], + t: ISubscription['t'], + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + const subscriptions = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); + + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } +} + export async function notifyOnSubscriptionChangedByUserIdAndRoomType( uid: ISubscription['u']['_id'], t: ISubscription['t'], From 937287c9b8c71c91e5c80fffa3343a6477832e8f Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Wed, 3 Jul 2024 23:02:43 -0300 Subject: [PATCH 15/38] refactor: switch notifier function params names --- apps/meteor/app/federation/server/endpoints/dispatch.js | 6 +++--- apps/meteor/app/lib/server/functions/createDirectRoom.ts | 6 +++--- apps/meteor/app/lib/server/functions/removeUserFromRoom.ts | 4 ++-- apps/meteor/app/lib/server/lib/notifyListener.ts | 4 ++-- apps/meteor/app/livechat/server/lib/Helper.ts | 4 ++-- .../app/message-mark-as-unread/server/unreadMessages.ts | 6 +++--- apps/meteor/app/threads/server/functions.ts | 6 +++--- .../message-read-receipt/server/hooks/afterSaveMessage.ts | 4 ++-- apps/meteor/server/lib/readMessages.ts | 4 ++-- apps/meteor/server/methods/hideRoom.ts | 4 ++-- apps/meteor/server/methods/openRoom.ts | 4 ++-- apps/meteor/server/methods/removeUserFromRoom.ts | 4 ++-- apps/meteor/server/methods/toggleFavorite.ts | 4 ++-- apps/meteor/server/models/raw/Roles.ts | 6 +++--- .../federation/infrastructure/rocket-chat/adapters/Room.ts | 6 +++--- apps/meteor/server/services/team/service.ts | 4 ++-- 16 files changed, 38 insertions(+), 38 deletions(-) diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index 9318007253b7..0bcce68ef059 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -11,7 +11,7 @@ import { notifyOnRoomChanged, notifyOnRoomChangedById, notifyOnSubscriptionChangedById, - notifyOnSubscriptionChangedByUserAndRoomId, + notifyOnSubscriptionChangedByRoomIdAndUserId, } from '../../../lib/server/lib/notifyListener'; import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage'; import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage'; @@ -188,7 +188,7 @@ const eventHandlers = { const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); if (deletedSubscription) { - void notifyOnSubscriptionChangedByUserAndRoomId(user._id, roomId, 'removed'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(roomId, user._id, 'removed'); } // Refresh the servers list @@ -220,7 +220,7 @@ const eventHandlers = { const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); if (deletedSubscription) { - void notifyOnSubscriptionChangedByUserAndRoomId(user._id, roomId, 'removed'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(roomId, user._id, 'removed'); } // Refresh the servers list diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index 85b2fe297149..f77ee1f55901 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -11,7 +11,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { isTruthy } from '../../../../lib/isTruthy'; import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; -import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByUserAndRoomId } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../lib/notifyListener'; const generateSubscription = ( fname: string, @@ -147,7 +147,7 @@ export async function createDirectRoom( { upsert: true }, ); if (modifiedCount || upsertedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(roomMembers[0]._id, rid, modifiedCount ? 'updated' : 'inserted'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, roomMembers[0]._id, modifiedCount ? 'updated' : 'inserted'); } } else { const memberIds = roomMembers.map((member) => member._id); @@ -170,7 +170,7 @@ export async function createDirectRoom( { upsert: true }, ); if (modifiedCount || upsertedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(member._id, rid, modifiedCount ? 'updated' : 'inserted'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, member._id, modifiedCount ? 'updated' : 'inserted'); } } } diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts index 47fd28d6588e..0fed092cf4df 100644 --- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts +++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts @@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback'; import { beforeLeaveRoomCallback } from '../../../../lib/callbacks/beforeLeaveRoomCallback'; -import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByUserAndRoomId } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../lib/notifyListener'; export const removeUserFromRoom = async function ( rid: string, @@ -62,7 +62,7 @@ export const removeUserFromRoom = async function ( const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, user._id); if (deletedSubscription) { - void notifyOnSubscriptionChangedByUserAndRoomId(user._id, rid, 'removed'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, user._id, 'removed'); } if (room.teamId && room.teamMain) { diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index f75e8dbf4489..d658a2d524a5 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -504,9 +504,9 @@ export async function notifyOnUserChangeById({ clientAction, id }: { id: IUser[' void notifyOnUserChange({ id, clientAction, data: user }); } -export async function notifyOnSubscriptionChangedByUserAndRoomId( - uid: ISubscription['u']['_id'], +export async function notifyOnSubscriptionChangedByRoomIdAndUserId( rid: ISubscription['rid'], + uid: ISubscription['u']['_id'], clientAction: ClientAction = 'updated', ): Promise { if (!dbWatchersDisabled) { diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 27311fa46b8e..68eb390dbf0c 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -42,7 +42,7 @@ import { notifyOnLivechatDepartmentAgentChangedByAgentsAndDepartmentId, notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomId, - notifyOnSubscriptionChangedByUserAndRoomId, + notifyOnSubscriptionChangedByRoomIdAndUserId, } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { Livechat as LivechatTyped } from './LivechatTyped'; @@ -285,7 +285,7 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username } const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, _id); if (deletedSubscription) { - void notifyOnSubscriptionChangedByUserAndRoomId(_id, rid, 'removed'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, _id, 'removed'); } await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name }); diff --git a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts index 098a3543ef15..2013171cbf8f 100644 --- a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts +++ b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts @@ -3,7 +3,7 @@ import { Messages, Subscriptions } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../lib/server/lib/notifyListener'; import logger from './logger'; declare module '@rocket.chat/ui-contexts' { @@ -39,7 +39,7 @@ Meteor.methods({ const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts); if (setAsUnreadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(userId, lastMessage.rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(lastMessage.rid, userId); } return; @@ -81,7 +81,7 @@ Meteor.methods({ logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`); const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts); if (setAsUnreadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(userId, originalMessage.rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(originalMessage.rid, userId); } }, }); diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index fe2e6a1e928d..194e482c54ae 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -4,7 +4,7 @@ import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocke import { notifyOnSubscriptionChangedByRoomIdAndUserIds, - notifyOnSubscriptionChangedByUserAndRoomId, + notifyOnSubscriptionChangedByRoomIdAndUserId, } from '../../lib/server/lib/notifyListener'; import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; @@ -70,7 +70,7 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); if (removeUnreadThreadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } await Messages.removeThreadFollowerByThreadId(tmid, uid); @@ -87,7 +87,7 @@ export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: s const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); if (removeUnreadThreadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(userId, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId); } await NotificationQueue.clearQueueByUserId(userId); diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts index 9511ddee9b0c..921bb2443f1b 100644 --- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts +++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts @@ -2,7 +2,7 @@ import type { IRoom, IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; import { Subscriptions } from '@rocket.chat/models'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../../../../app/lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../../../app/lib/server/lib/notifyListener'; import { callbacks } from '../../../../../lib/callbacks'; import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt'; @@ -18,7 +18,7 @@ callbacks.add( // set subscription as read right after message was sent const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id); if (setAsReadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(message.u._id, room._id); + void notifyOnSubscriptionChangedByRoomIdAndUserId(room._id, message.u._id); } } diff --git a/apps/meteor/server/lib/readMessages.ts b/apps/meteor/server/lib/readMessages.ts index e9ac09de0b8d..3be43a875fac 100644 --- a/apps/meteor/server/lib/readMessages.ts +++ b/apps/meteor/server/lib/readMessages.ts @@ -1,7 +1,7 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; import { NotificationQueue, Subscriptions } from '@rocket.chat/models'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; import { callbacks } from '../../lib/callbacks'; export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThreads: boolean): Promise { @@ -18,7 +18,7 @@ export async function readMessages(rid: IRoom['_id'], uid: IUser['_id'], readThr const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, readThreads, alert); if (setAsReadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } await NotificationQueue.clearQueueByUserId(uid); diff --git a/apps/meteor/server/methods/hideRoom.ts b/apps/meteor/server/methods/hideRoom.ts index 5955fcd747eb..4719c09935d4 100644 --- a/apps/meteor/server/methods/hideRoom.ts +++ b/apps/meteor/server/methods/hideRoom.ts @@ -3,7 +3,7 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -24,7 +24,7 @@ export const hideRoomMethod = async (userId: string, rid: string): Promise({ const openByRoomResponse = await Subscriptions.openByRoomIdAndUserId(rid, uid); if (openByRoomResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } return openByRoomResponse.modifiedCount; diff --git a/apps/meteor/server/methods/removeUserFromRoom.ts b/apps/meteor/server/methods/removeUserFromRoom.ts index eb796e657199..ef4613febb4c 100644 --- a/apps/meteor/server/methods/removeUserFromRoom.ts +++ b/apps/meteor/server/methods/removeUserFromRoom.ts @@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync, getUsersInRole } from '../../app/authorization/server'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../app/authorization/server/functions/hasRole'; -import { notifyOnRoomChanged, notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; import { RoomMemberActions } from '../../definition/IRoomTypeConfig'; import { callbacks } from '../../lib/callbacks'; import { afterRemoveFromRoomCallback } from '../../lib/callbacks/afterRemoveFromRoomCallback'; @@ -79,7 +79,7 @@ export const removeUserFromRoomMethod = async (fromId: string, data: { rid: stri const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id); if (deletedSubscription) { - void notifyOnSubscriptionChangedByUserAndRoomId(removedUser._id, data.rid, 'removed'); + void notifyOnSubscriptionChangedByRoomIdAndUserId(data.rid, removedUser._id, 'removed'); } if (['c', 'p'].includes(room.t) === true) { diff --git a/apps/meteor/server/methods/toggleFavorite.ts b/apps/meteor/server/methods/toggleFavorite.ts index 6f3ba5f7e1a2..632f894a82f9 100644 --- a/apps/meteor/server/methods/toggleFavorite.ts +++ b/apps/meteor/server/methods/toggleFavorite.ts @@ -4,7 +4,7 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../app/lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -33,7 +33,7 @@ Meteor.methods({ const { modifiedCount } = await Subscriptions.setFavoriteByRoomIdAndUserId(rid, userId, f); if (modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(userId, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId); } return modifiedCount; diff --git a/apps/meteor/server/models/raw/Roles.ts b/apps/meteor/server/models/raw/Roles.ts index f62bb9cf8356..219e4a8a1302 100644 --- a/apps/meteor/server/models/raw/Roles.ts +++ b/apps/meteor/server/models/raw/Roles.ts @@ -3,7 +3,7 @@ import type { IRolesModel } from '@rocket.chat/model-typings'; import { Subscriptions, Users } from '@rocket.chat/models'; import type { Collection, FindCursor, Db, Filter, FindOptions, Document } from 'mongodb'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../../app/lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../app/lib/server/lib/notifyListener'; import { BaseRaw } from './BaseRaw'; export class RolesRaw extends BaseRaw implements IRolesModel { @@ -40,7 +40,7 @@ export class RolesRaw extends BaseRaw implements IRolesModel { if (role.scope === 'Subscriptions' && scope) { const addRolesResponse = await Subscriptions.addRolesByUserId(userId, [role._id], scope); if (addRolesResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(userId, scope); + void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId); } } else { await Users.addRolesByUserId(userId, [role._id]); @@ -92,7 +92,7 @@ export class RolesRaw extends BaseRaw implements IRolesModel { if (role.scope === 'Subscriptions' && scope) { const removeRolesResponse = await Subscriptions.removeRolesByUserId(userId, [roleId], scope); if (removeRolesResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(userId, scope); + void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId); } } else { await Users.removeRolesByUserId(userId, [roleId]); diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts index c783e175f1ce..32f939d81c56 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts @@ -10,7 +10,7 @@ import { removeUserFromRoom } from '../../../../../../app/lib/server/functions/r import { notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomId, - notifyOnSubscriptionChangedByUserAndRoomId, + notifyOnSubscriptionChangedByRoomIdAndUserId, } from '../../../../../../app/lib/server/lib/notifyListener'; import { settings } from '../../../../../../app/settings/server'; import { getDefaultSubscriptionPref } from '../../../../../../app/utils/lib/getDefaultSubscriptionPref'; @@ -301,7 +301,7 @@ export class RocketChatRoomAdapter { if (toAdd.length > 0) { const addRolesResponse = await Subscriptions.addRolesByUserId(uid, toAdd, rid); if (addRolesResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } if (notifyChannel) { @@ -322,7 +322,7 @@ export class RocketChatRoomAdapter { if (toRemove.length > 0) { const removeRolesResponse = await Subscriptions.removeRolesByUserId(uid, toRemove, rid); if (removeRolesResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(uid, rid); + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } if (notifyChannel) { diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index 84a1d9d5aa98..d958772d5aa1 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -32,7 +32,7 @@ import { addUserToRoom } from '../../../app/lib/server/functions/addUserToRoom'; import { checkUsernameAvailability } from '../../../app/lib/server/functions/checkUsernameAvailability'; import { getSubscribedRoomsForUserWithDetails } from '../../../app/lib/server/functions/getRoomsWithSingleOwner'; import { removeUserFromRoom } from '../../../app/lib/server/functions/removeUserFromRoom'; -import { notifyOnSubscriptionChangedByUserAndRoomId } from '../../../app/lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../app/lib/server/lib/notifyListener'; import { settings } from '../../../app/settings/server'; export class TeamService extends ServiceClassInternal implements ITeamService { @@ -756,7 +756,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService { ]); if (responses[1].modifiedCount) { - void notifyOnSubscriptionChangedByUserAndRoomId(member.userId, team.roomId); + void notifyOnSubscriptionChangedByRoomIdAndUserId(team.roomId, member.userId); } } From 490d60e97ca387b3893a5e6cf201c0a2e45d30e4 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Mon, 29 Jul 2024 12:45:48 -0300 Subject: [PATCH 16/38] chore: typecheck fix --- apps/meteor/app/lib/server/methods/unblockUser.ts | 2 +- apps/meteor/server/methods/hideRoom.ts | 2 +- apps/meteor/server/methods/ignoreUser.ts | 2 +- apps/meteor/server/methods/openRoom.ts | 2 +- apps/meteor/server/methods/toggleFavorite.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/lib/server/methods/unblockUser.ts b/apps/meteor/app/lib/server/methods/unblockUser.ts index 47fa7f6468ee..7b4bc5660010 100644 --- a/apps/meteor/app/lib/server/methods/unblockUser.ts +++ b/apps/meteor/app/lib/server/methods/unblockUser.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { unblockUser({ rid, blocked }: { rid: string; blocked: string }): boolean; diff --git a/apps/meteor/server/methods/hideRoom.ts b/apps/meteor/server/methods/hideRoom.ts index 21d95beb5aaf..1fd4c6b4657e 100644 --- a/apps/meteor/server/methods/hideRoom.ts +++ b/apps/meteor/server/methods/hideRoom.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { hideRoom(rid: string): Promise; diff --git a/apps/meteor/server/methods/ignoreUser.ts b/apps/meteor/server/methods/ignoreUser.ts index b035f2ea906f..a8739a910b37 100644 --- a/apps/meteor/server/methods/ignoreUser.ts +++ b/apps/meteor/server/methods/ignoreUser.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { notifyOnSubscriptionChangedById } from '../../app/lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { ignoreUser(params: { rid: string; userId: string; ignore?: boolean }): boolean; diff --git a/apps/meteor/server/methods/openRoom.ts b/apps/meteor/server/methods/openRoom.ts index d9f14bfd1191..440de52b87fb 100644 --- a/apps/meteor/server/methods/openRoom.ts +++ b/apps/meteor/server/methods/openRoom.ts @@ -6,7 +6,7 @@ import { Meteor } from 'meteor/meteor'; import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { openRoom(rid: IRoom['_id']): Promise; diff --git a/apps/meteor/server/methods/toggleFavorite.ts b/apps/meteor/server/methods/toggleFavorite.ts index be6813de0d13..912b9a8f3e5c 100644 --- a/apps/meteor/server/methods/toggleFavorite.ts +++ b/apps/meteor/server/methods/toggleFavorite.ts @@ -6,7 +6,7 @@ import { Meteor } from 'meteor/meteor'; import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { toggleFavorite(rid: IRoom['_id'], f?: boolean): Promise; From bbc0e8450ea4ae113224a7b4fc1c78e39549dac6 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 5 Aug 2024 18:33:55 -0300 Subject: [PATCH 17/38] fix notify on message --- .../app/lib/server/lib/notifyListener.ts | 6 ++ .../lib/server/lib/notifyUsersOnMessage.ts | 89 +++++++------------ .../meteor/server/models/raw/Subscriptions.ts | 27 ++++++ .../src/models/ISubscriptionsModel.ts | 10 +++ 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index b9f528d65f4f..d74b11eea45c 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -473,6 +473,12 @@ export const notifyOnMessageChange = withDbWatcherCheck(async ({ id, data }: { i void api.broadcast('watch.messages', { message }); }); +export const notifyOnSubscriptionChanged = withDbWatcherCheck( + async (subscription: ISubscription, clientAction: ClientAction = 'updated'): Promise => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }, +); + export async function notifyOnSubscriptionChangedByRoomIdAndUserId( rid: ISubscription['rid'], uid: ISubscription['u']['_id'], diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 889be206b9a0..6a900c47dd9c 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -3,11 +3,10 @@ import { isEditedMessage } from '@rocket.chat/core-typings'; import { Subscriptions, Rooms } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import moment from 'moment'; -import type { Document, UpdateResult } from 'mongodb'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; -import { notifyOnSubscriptionChangedByRoomIdExcludingUserIds, notifyOnSubscriptionChangedByRoomIdAndUserIds } from './notifyListener'; +import { notifyOnSubscriptionChanged, notifyOnSubscriptionChangedByRoomIdAndUserIds } from './notifyListener'; function messageContainsHighlight(message: IMessage, highlights: string[]): boolean { if (!highlights || highlights.length === 0) return false; @@ -52,26 +51,14 @@ export async function getMentions(message: IMessage): Promise<{ toAll: boolean; type UnreadCountType = 'all_messages' | 'user_mentions_only' | 'group_mentions_only' | 'user_and_group_mentions_only'; -const incGroupMentions = async ( - rid: IRoom['_id'], - roomType: RoomType, - excludeUserId: IUser['_id'], - unreadCount: Exclude, -): Promise => { +const getGroupMentions = (roomType: RoomType, unreadCount: Exclude): number => { const incUnreadByGroup = ['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); - const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0; - return Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread); + return roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0; }; -const incUserMentions = async ( - rid: IRoom['_id'], - roomType: RoomType, - uids: IUser['_id'][], - unreadCount: Exclude, -): Promise => { - const incUnreadByUser = new Set(['all_messages', 'user_mentions_only', 'user_and_group_mentions_only']).has(unreadCount); - const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0; - return Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread); +const getUserMentions = (roomType: RoomType, unreadCount: Exclude): number => { + const incUnreadByUser = ['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); + return roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0; }; export const getUserIdsFromHighlights = async (rid: IRoom['_id'], message: IMessage): Promise => { @@ -112,55 +99,45 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise const userIds = [...new Set([...mentionIds, ...highlightIds])]; const unreadCount = getUnreadSettingCount(room.t); - const includeUsers = new Set(); - const excludeUsers = new Set(); + const userMentionInc = getUserMentions(room.t, unreadCount as Exclude); + const groupMentionInc = getGroupMentions(room.t, unreadCount as Exclude); + + Subscriptions.findByRoomIdAndNotAlertOrOpenExcludingUserIds({ + roomId: room._id, + uidsExclude: [message.u._id], + uidsInclude: userIds, + onlyRead: !toAll && !toHere, + }).forEach((sub) => { + const hasUserMention = userIds.includes(sub.u._id); + const shouldIncUnread = hasUserMention || toAll || toHere || unreadCount === 'all_messages'; + void notifyOnSubscriptionChanged( + { + ...sub, + alert: true, + open: true, + ...(shouldIncUnread && { unread: sub.unread + 1 }), + ...(hasUserMention && { userMentions: sub.userMentions + 1 }), + ...((toAll || toHere) && { groupMentions: sub.groupMentions + 1 }), + }, + 'updated', + ); + }); // Give priority to user mentions over group mentions if (userIds.length) { - const incUserMentionsResponse = await incUserMentions( - room._id, - room.t, - userIds, - unreadCount as Exclude, - ); - if (incUserMentionsResponse.modifiedCount) { - userIds.forEach((userId) => includeUsers.add(userId)); - } + await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, userIds, 1, userMentionInc); } else if (toAll || toHere) { - const incGroupMentionsResponse = await incGroupMentions( - room._id, - room.t, - message.u._id, - unreadCount as Exclude, - ); - if (incGroupMentionsResponse.modifiedCount) { - excludeUsers.add(message.u._id); - } + await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, groupMentionInc); } if (!toAll && !toHere && unreadCount === 'all_messages') { - const incUnreadForRoomIdResponse = await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); - if (incUnreadForRoomIdResponse.modifiedCount) { - [userIds, message.u._id].flat().forEach((userId) => excludeUsers.add(userId)); - } + await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); } - const [alertResponse, openResponse] = await Promise.all([ + await Promise.all([ Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id), Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id), ]); - - if (alertResponse.modifiedCount || openResponse.modifiedCount) { - excludeUsers.add(message.u._id); - } - - if (includeUsers.size) { - void notifyOnSubscriptionChangedByRoomIdExcludingUserIds(message.rid, [...excludeUsers]); - } - - if (excludeUsers.size) { - void notifyOnSubscriptionChangedByRoomIdAndUserIds(message.rid, [...includeUsers]); - } } export async function updateThreadUsersSubscriptions(message: IMessage, replies: IUser['_id'][]): Promise { diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts index b1dc865ceb85..a8959d1c2e6e 100644 --- a/apps/meteor/server/models/raw/Subscriptions.ts +++ b/apps/meteor/server/models/raw/Subscriptions.ts @@ -327,6 +327,33 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.find(query, options || {}); } + findByRoomIdAndNotAlertOrOpenExcludingUserIds( + { + roomId, + uidsExclude, + uidsInclude, + onlyRead, + }: { + roomId: ISubscription['rid']; + uidsExclude?: ISubscription['u']['_id'][]; + uidsInclude?: ISubscription['u']['_id'][]; + onlyRead: boolean; + }, + options?: FindOptions, + ) { + const query = { + rid: roomId, + ...(uidsExclude?.length && { + 'u._id': { $nin: uidsExclude }, + }), + ...(onlyRead && { + $or: [...(uidsInclude?.length ? [{ 'u._id': { $in: uidsInclude } }] : []), { alert: { $ne: true } }, { open: { $ne: true } }], + }), + }; + + return this.find(query, options || {}); + } + async removeByRoomId(roomId: ISubscription['rid']): Promise { const query = { rid: roomId, diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index 76e2600dfe3b..f66c52315e46 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -73,6 +73,16 @@ export interface ISubscriptionsModel extends IBaseModel { findByUserIdAndTypes(userId: string, types: ISubscription['t'][], options?: FindOptions): FindCursor; + findByRoomIdAndNotAlertOrOpenExcludingUserIds( + filter: { + roomId: ISubscription['rid']; + uidsExclude?: ISubscription['u']['_id'][]; + uidsInclude?: ISubscription['u']['_id'][]; + onlyRead: boolean; + }, + options?: FindOptions, + ): FindCursor; + removeByRoomId(roomId: ISubscription['rid']): Promise; findByRoomIdExcludingUserIds( From 223f999642148823eaa44788bdf484ed71defb25 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 7 Aug 2024 15:55:52 -0300 Subject: [PATCH 18/38] remove then --- .../services/omnichannel.internalService.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts index 1e1cda122352..f2db61ddbc9a 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.internalService.ts @@ -57,19 +57,19 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE throw new Error('error-unserved-rooms-cannot-be-placed-onhold'); } - await Promise.all([ + const [roomResult, subsResult] = await Promise.all([ LivechatRooms.setOnHoldByRoomId(roomId), Subscriptions.setOnHoldByRoomId(roomId), Message.saveSystemMessage('omnichannel_placed_chat_on_hold', roomId, '', onHoldBy, { comment }), - ]).then((data) => { - if (data[0].modifiedCount) { - void notifyOnRoomChangedById(roomId); - } + ]); - if (data[1].modifiedCount) { - void notifyOnSubscriptionChangedByRoomId(roomId); - } - }); + if (roomResult.modifiedCount) { + void notifyOnRoomChangedById(roomId); + } + + if (subsResult.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId); + } await callbacks.run('livechat:afterOnHold', room); } @@ -114,19 +114,19 @@ export class OmnichannelEE extends ServiceClassInternal implements IOmnichannelE clientAction, }); - await Promise.all([ + const [roomResult, subsResult] = await Promise.all([ LivechatRooms.unsetOnHoldByRoomId(roomId), Subscriptions.unsetOnHoldByRoomId(roomId), Message.saveSystemMessage('omnichannel_on_hold_chat_resumed', roomId, '', resumeBy, { comment }), - ]).then((data) => { - if (data[0].modifiedCount) { - void notifyOnRoomChangedById(roomId); - } + ]); - if (data[1].modifiedCount) { - void notifyOnSubscriptionChangedByRoomId(roomId); - } - }); + if (roomResult.modifiedCount) { + void notifyOnRoomChangedById(roomId); + } + + if (subsResult.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(roomId); + } await callbacks.run('livechat:afterOnHoldChatResumed', room); } From 0154d88dd97e3a1ac11db7011e577653eb9cc32b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 7 Aug 2024 16:54:09 -0300 Subject: [PATCH 19/38] remove redundant update --- .../server/hooks/afterSaveMessage.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts index 921bb2443f1b..a2241df90dbf 100644 --- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts +++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts @@ -1,8 +1,6 @@ import type { IRoom, IMessage } from '@rocket.chat/core-typings'; -import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { Subscriptions } from '@rocket.chat/models'; +import { isEditedMessage } from '@rocket.chat/core-typings'; -import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../../../app/lib/server/lib/notifyListener'; import { callbacks } from '../../../../../lib/callbacks'; import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt'; @@ -14,14 +12,6 @@ callbacks.add( return message; } - if (!isOmnichannelRoom(room) || !room.closedAt) { - // set subscription as read right after message was sent - const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id); - if (setAsReadResponse.modifiedCount) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(room._id, message.u._id); - } - } - // mark message as read as well await ReadReceipt.markMessageAsReadBySender(message, room, message.u._id); From 26eac69d7f13332cb0feba70702488eae493db2f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 7 Aug 2024 17:54:08 -0300 Subject: [PATCH 20/38] void them --- apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 6a900c47dd9c..776a5c6d4d71 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -102,7 +102,7 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise const userMentionInc = getUserMentions(room.t, unreadCount as Exclude); const groupMentionInc = getGroupMentions(room.t, unreadCount as Exclude); - Subscriptions.findByRoomIdAndNotAlertOrOpenExcludingUserIds({ + void Subscriptions.findByRoomIdAndNotAlertOrOpenExcludingUserIds({ roomId: room._id, uidsExclude: [message.u._id], uidsInclude: userIds, From 5f59554b756e556ca66cd103f7772daf7c7d701e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 9 Aug 2024 15:35:34 -0300 Subject: [PATCH 21/38] remove find --- apps/meteor/app/e2e/server/methods/updateGroupKey.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts index 384dbd85a4e2..87182f723e7d 100644 --- a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts +++ b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts @@ -3,7 +3,7 @@ import { Subscriptions } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../lib/server/lib/notifyListener'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -33,11 +33,10 @@ Meteor.methods({ return; } - const userSub = await Subscriptions.findOneByRoomIdAndUserId(rid, uid); - if (userSub) { - // uid also has subscription to this room - await Subscriptions.setGroupE2ESuggestedKey(uid, rid, key); - void notifyOnSubscriptionChangedById(userSub._id); + // uid also has subscription to this room + const { modifiedCount } = await Subscriptions.setGroupE2ESuggestedKey(uid, rid, key); + if (modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); } } }, From 7af2032ac2e5a332c43b6252aad744131c4735a9 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 9 Aug 2024 15:39:15 -0300 Subject: [PATCH 22/38] remove redundant function --- .../lib/server/functions/setUserActiveStatus.ts | 4 ++-- apps/meteor/app/lib/server/lib/notifyListener.ts | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts index 8e376c2f8ec6..26011637e06d 100644 --- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts +++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts @@ -11,7 +11,7 @@ import { settings } from '../../../settings/server'; import { notifyOnRoomChangedById, notifyOnRoomChangedByUserDM, - notifySubscriptionsOnUserArchived, + notifyOnSubscriptionChangedByUserIdAndRoomType, notifyOnUserChange, } from '../lib/notifyListener'; import { closeOmnichannelConversations } from './closeOmnichannelConversations'; @@ -110,7 +110,7 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi if (user.username) { const setArchivedResponse = await Subscriptions.setArchivedByUsername(user.username, !active); if (setArchivedResponse.modifiedCount) { - void notifySubscriptionsOnUserArchived(user._id, 'd'); + void notifyOnSubscriptionChangedByUserIdAndRoomType(user._id, 'd'); } } diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index d74b11eea45c..367a094f2f97 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -607,22 +607,6 @@ export async function notifyOnSubscriptionChangedByAutoTranslateAndUserId( } } -export async function notifySubscriptionsOnUserArchived( - uid: ISubscription['u']['_id'], - t: ISubscription['t'], - clientAction: ClientAction = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } - - const subscriptions = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); - - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} - export async function notifyOnSubscriptionChangedByUserIdAndRoomType( uid: ISubscription['u']['_id'], t: ISubscription['t'], From aaec1143bb8c1ae802c6757950a836251f49e890 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 9 Aug 2024 16:00:35 -0300 Subject: [PATCH 23/38] fix update by name --- .../app/lib/server/functions/saveUserIdentity.ts | 5 ++++- .../lib/server/functions/setUserActiveStatus.ts | 8 ++++---- apps/meteor/app/lib/server/lib/notifyListener.ts | 6 ++---- apps/meteor/server/models/raw/Subscriptions.ts | 15 ++++++++------- .../src/models/ISubscriptionsModel.ts | 4 +--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts index 3258919ea700..1729a1ba8abd 100644 --- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts +++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts @@ -160,7 +160,10 @@ async function updateUsernameReferences({ ); if (updateDirectNameResponse?.modifiedCount) { - void notifyOnSubscriptionChangedByNameAndRoomType(rawUsername && username, rawName && name, 'd'); + void notifyOnSubscriptionChangedByNameAndRoomType({ + t: 'd', + name: username, + }); } // update name and fname of group direct messages diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts index 26011637e06d..c6bb4086f486 100644 --- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts +++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts @@ -11,7 +11,7 @@ import { settings } from '../../../settings/server'; import { notifyOnRoomChangedById, notifyOnRoomChangedByUserDM, - notifyOnSubscriptionChangedByUserIdAndRoomType, + notifyOnSubscriptionChangedByNameAndRoomType, notifyOnUserChange, } from '../lib/notifyListener'; import { closeOmnichannelConversations } from './closeOmnichannelConversations'; @@ -108,9 +108,9 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi } if (user.username) { - const setArchivedResponse = await Subscriptions.setArchivedByUsername(user.username, !active); - if (setArchivedResponse.modifiedCount) { - void notifyOnSubscriptionChangedByUserIdAndRoomType(user._id, 'd'); + const { modifiedCount } = await Subscriptions.setArchivedByUsername(user.username, !active); + if (modifiedCount) { + void notifyOnSubscriptionChangedByNameAndRoomType({ t: 'd', name: user.username }); } } diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 367a094f2f97..750737a561d7 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -624,16 +624,14 @@ export async function notifyOnSubscriptionChangedByUserIdAndRoomType( } export async function notifyOnSubscriptionChangedByNameAndRoomType( - name?: ISubscription['name'], - fname?: ISubscription['fname'], - t?: ISubscription['t'], + filter: Partial>, clientAction: ClientAction = 'updated', ): Promise { if (!dbWatchersDisabled) { return; } - const subscriptions = Subscriptions.findByNameAndRoomType(name, fname, t, { projection: subscriptionFields }); + const subscriptions = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields }); for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts index a8959d1c2e6e..a66e597f13f6 100644 --- a/apps/meteor/server/models/raw/Subscriptions.ts +++ b/apps/meteor/server/models/raw/Subscriptions.ts @@ -1304,15 +1304,16 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri } findByNameAndRoomType( - name?: ISubscription['name'], - fname?: ISubscription['fname'], - type?: ISubscription['t'], + filter: Partial>, options?: FindOptions, ): FindCursor { - const query: Filter = {}; - if (name) query.name = name; - if (fname) query.fname = fname; - if (type) query.t = type; + if (!filter.name && !filter.t) { + throw new Error('invalid filter'); + } + const query: Filter = { + ...(filter.name && { name: filter.name }), + ...(filter.t && { t: filter.t }), + }; return this.find(query, options); } diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index f66c52315e46..bbc25c162794 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -168,9 +168,7 @@ export interface ISubscriptionsModel extends IBaseModel { options?: FindOptions, ): FindCursor; findByNameAndRoomType( - name?: ISubscription['name'], - fname?: ISubscription['fname'], - type?: ISubscription['t'], + filter: Partial>, options?: FindOptions, ): FindCursor; From 2b2e0e038b3ba38ec51bb21ba3a978f5cd921c94 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 9 Aug 2024 17:57:41 -0300 Subject: [PATCH 24/38] add TODO back --- apps/meteor/server/models/raw/Roles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/server/models/raw/Roles.ts b/apps/meteor/server/models/raw/Roles.ts index 219e4a8a1302..4e1cb09348c4 100644 --- a/apps/meteor/server/models/raw/Roles.ts +++ b/apps/meteor/server/models/raw/Roles.ts @@ -38,6 +38,7 @@ export class RolesRaw extends BaseRaw implements IRolesModel { } if (role.scope === 'Subscriptions' && scope) { + // TODO remove dependency from other models - this logic should be inside a function/service const addRolesResponse = await Subscriptions.addRolesByUserId(userId, [role._id], scope); if (addRolesResponse.modifiedCount) { void notifyOnSubscriptionChangedByRoomIdAndUserId(scope, userId); From 63e05e67df13b364736401a74d66a9425bc548f3 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Aug 2024 16:09:40 -0300 Subject: [PATCH 25/38] exclude delete operations --- .../app/lib/server/lib/notifyListener.ts | 66 ++++--------------- .../app/livechat/server/lib/LivechatTyped.ts | 14 ++-- 2 files changed, 20 insertions(+), 60 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 750737a561d7..3611ad56a19d 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -474,7 +474,7 @@ export const notifyOnMessageChange = withDbWatcherCheck(async ({ id, data }: { i }); export const notifyOnSubscriptionChanged = withDbWatcherCheck( - async (subscription: ISubscription, clientAction: ClientAction = 'updated'): Promise => { + async (subscription: ISubscription, clientAction: Exclude = 'updated'): Promise => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); }, ); @@ -502,13 +502,15 @@ export async function notifyOnSubscriptionChangedByRoomIdAndUserId( } } -export async function notifyOnSubscriptionChangedById(id: ISubscription['_id'], clientAction: ClientAction = 'updated'): Promise { +export async function notifyOnSubscriptionChangedById( + id: ISubscription['_id'], + clientAction: Exclude = 'updated', +): Promise { if (!dbWatchersDisabled) { return; } - const subscription = clientAction === 'removed' ? await Subscriptions.trashFindOneById(id) : await Subscriptions.findOneById(id); - + const subscription = await Subscriptions.findOneById(id); if (!subscription) { return; } @@ -516,27 +518,11 @@ export async function notifyOnSubscriptionChangedById(id: ISubscription['_id'], void api.broadcast('watch.subscriptions', { clientAction, subscription }); } -export async function notifyOnSubscriptionChangedByRoomIdExcludingUserIds( - rid: ISubscription['rid'], - uids: ISubscription['u']['_id'][], - clientAction: ClientAction = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } - - const subscriptions = Subscriptions.findByRoomIdExcludingUserIds(rid, uids, { projection: subscriptionFields }); - - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} - export async function notifyOnSubscriptionChangedByUserPreferences( uid: ISubscription['u']['_id'], notificationOriginField: keyof ISubscription, originFieldNotEqualValue: 'user' | 'subscription', - clientAction: ClientAction = 'updated', + clientAction: Exclude = 'updated', ): Promise { if (!dbWatchersDisabled) { return; @@ -573,28 +559,9 @@ export async function notifyOnSubscriptionChangedByRoomId( } } -export async function notifyOnSubscriptionChangedByToken(token: string, clientAction: ClientAction = 'updated'): Promise { - if (!dbWatchersDisabled) { - return; - } - - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ 'v.token': token }, { projection: subscriptionFields }) - : Subscriptions.findByToken(token, { projection: subscriptionFields }); - - if (!subscriptions) { - return; - } - - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} - export async function notifyOnSubscriptionChangedByAutoTranslateAndUserId( uid: ISubscription['u']['_id'], - clientAction: ClientAction = 'updated', + clientAction: Exclude = 'updated', ): Promise { if (!dbWatchersDisabled) { return; @@ -610,7 +577,7 @@ export async function notifyOnSubscriptionChangedByAutoTranslateAndUserId( export async function notifyOnSubscriptionChangedByUserIdAndRoomType( uid: ISubscription['u']['_id'], t: ISubscription['t'], - clientAction: ClientAction = 'updated', + clientAction: Exclude = 'updated', ): Promise { if (!dbWatchersDisabled) { return; @@ -625,7 +592,7 @@ export async function notifyOnSubscriptionChangedByUserIdAndRoomType( export async function notifyOnSubscriptionChangedByNameAndRoomType( filter: Partial>, - clientAction: ClientAction = 'updated', + clientAction: Exclude = 'updated', ): Promise { if (!dbWatchersDisabled) { return; @@ -640,7 +607,7 @@ export async function notifyOnSubscriptionChangedByNameAndRoomType( export async function notifyOnSubscriptionChangedByUserId( uid: ISubscription['u']['_id'], - clientAction: ClientAction = 'updated', + clientAction: Exclude = 'updated', ): Promise { if (!dbWatchersDisabled) { return; @@ -656,20 +623,13 @@ export async function notifyOnSubscriptionChangedByUserId( export async function notifyOnSubscriptionChangedByRoomIdAndUserIds( rid: ISubscription['rid'], uids: ISubscription['u']['_id'][], - clientAction: ClientAction = 'updated', + clientAction: Exclude = 'updated', ): Promise { if (!dbWatchersDisabled) { return; } - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ rid, 'u._id': { $in: uids } }, { projection: subscriptionFields }) - : Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); - - if (!subscriptions) { - return; - } + const subscriptions = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 89874530e855..36164ffc4ea8 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -63,7 +63,6 @@ import { notifyOnUserChange, notifyOnLivechatDepartmentAgentChangedByDepartmentId, notifyOnSubscriptionChangedByRoomId, - notifyOnSubscriptionChangedByToken, } from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; @@ -1159,19 +1158,20 @@ class LivechatClass { const cursor = LivechatRooms.findByVisitorToken(token); for await (const room of cursor) { - await Promise.all([ + const [{ deletedCount }] = await Promise.all([ + Subscriptions.removeByRoomId(room._id), FileUpload.removeFilesByRoomId(room._id), Messages.removeByRoomId(room._id), ReadReceipts.removeByRoomId(room._id), ]); - } - - const responses = await Promise.all([Subscriptions.removeByVisitorToken(token), LivechatRooms.removeByVisitorToken(token)]); - if (responses[0]?.deletedCount) { - void notifyOnSubscriptionChangedByToken(token, 'removed'); + if (deletedCount) { + void notifyOnSubscriptionChangedByRoomId(room._id, 'removed'); + } } + await LivechatRooms.removeByVisitorToken(token); + const livechatInquiries = await LivechatInquiry.findIdsByVisitorToken(token).toArray(); await LivechatInquiry.removeByIds(livechatInquiries.map(({ _id }) => _id)); void notifyOnLivechatInquiryChanged(livechatInquiries, 'removed'); From c3269e7f064a41ad69320af986fe8c2823d48b34 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Aug 2024 16:15:49 -0300 Subject: [PATCH 26/38] use existing publishFields --- .../app/lib/server/lib/notifyListener.ts | 50 +------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 3611ad56a19d..56541b403f6f 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -39,6 +39,7 @@ import { } from '@rocket.chat/models'; import mem from 'mem'; +import { subscriptionFields } from '../../../../lib/publishFields'; import { shouldHideSystemMessage } from '../../../../server/lib/systemMessage/hideSystemMessage'; type ClientAction = 'inserted' | 'updated' | 'removed'; @@ -635,52 +636,3 @@ export async function notifyOnSubscriptionChangedByRoomIdAndUserIds( void api.broadcast('watch.subscriptions', { clientAction, subscription }); } } - -const subscriptionFields = { - t: 1, - ts: 1, - ls: 1, - lr: 1, - name: 1, - fname: 1, - rid: 1, - code: 1, - f: 1, - u: 1, - open: 1, - alert: 1, - roles: 1, - unread: 1, - prid: 1, - userMentions: 1, - groupMentions: 1, - archived: 1, - audioNotificationValue: 1, - desktopNotifications: 1, - mobilePushNotifications: 1, - emailNotifications: 1, - desktopPrefOrigin: 1, - mobilePrefOrigin: 1, - emailPrefOrigin: 1, - unreadAlert: 1, - // _updatedAt: 1, - blocked: 1, - blocker: 1, - autoTranslate: 1, - autoTranslateLanguage: 1, - disableNotifications: 1, - hideUnreadStatus: 1, - hideMentionStatus: 1, - muteGroupMentions: 1, - ignored: 1, - E2EKey: 1, - E2ESuggestedKey: 1, - tunread: 1, - tunreadGroup: 1, - tunreadUser: 1, - - // Omnichannel fields - department: 1, - v: 1, - onHold: 1, -}; From fa846ea3a98f6d0d8032cb19993a642274ca77d4 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Aug 2024 16:53:26 -0300 Subject: [PATCH 27/38] fix notify on deleteUser --- apps/meteor/app/lib/server/functions/deleteUser.ts | 2 +- apps/meteor/app/lib/server/lib/notifyListener.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index 687758d4042f..8136f31946e1 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -115,7 +115,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele const deletedCount = await Subscriptions.removeByUserId(userId); if (deletedCount) { - void notifyOnSubscriptionChangedByUserId(userId); + void notifyOnSubscriptionChangedByUserId(userId, 'removed'); } // Remove user as livechat agent diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 56541b403f6f..fa329196d82a 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -608,13 +608,20 @@ export async function notifyOnSubscriptionChangedByNameAndRoomType( export async function notifyOnSubscriptionChangedByUserId( uid: ISubscription['u']['_id'], - clientAction: Exclude = 'updated', + clientAction: ClientAction = 'updated', ): Promise { if (!dbWatchersDisabled) { return; } - const subscriptions = Subscriptions.findByUserId(uid, { projection: subscriptionFields }); + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ 'u._id': uid }, { projection: subscriptionFields }) + : Subscriptions.findByUserId(uid, { projection: subscriptionFields }); + + if (!subscriptions) { + return; + } for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); From dbace9f25cdd77b240a390af1eea5a5e7b7b5709 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Aug 2024 17:00:31 -0300 Subject: [PATCH 28/38] use withDbWatcherCheck in all functions --- .../app/lib/server/lib/notifyListener.ts | 250 ++++++++---------- 1 file changed, 105 insertions(+), 145 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index fa329196d82a..865e8f0a56ca 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -464,9 +464,6 @@ export async function getMessageToBroadcast({ id, data }: { id: IMessage['_id']; } export const notifyOnMessageChange = withDbWatcherCheck(async ({ id, data }: { id: IMessage['_id']; data?: IMessage }): Promise => { - if (!dbWatchersDisabled) { - return; - } const message = await getMessageToBroadcast({ id, data }); if (!message) { return; @@ -480,166 +477,129 @@ export const notifyOnSubscriptionChanged = withDbWatcherCheck( }, ); -export async function notifyOnSubscriptionChangedByRoomIdAndUserId( - rid: ISubscription['rid'], - uid: ISubscription['u']['_id'], - clientAction: ClientAction = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } - - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ rid, 'u._id': uid }, { projection: subscriptionFields }) - : Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); - - if (!subscriptions) { - return; - } - - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} - -export async function notifyOnSubscriptionChangedById( - id: ISubscription['_id'], - clientAction: Exclude = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } - - const subscription = await Subscriptions.findOneById(id); - if (!subscription) { - return; - } - - void api.broadcast('watch.subscriptions', { clientAction, subscription }); -} +export const notifyOnSubscriptionChangedByRoomIdAndUserId = withDbWatcherCheck( + async (rid: ISubscription['rid'], uid: ISubscription['u']['_id'], clientAction: ClientAction = 'updated'): Promise => { + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ rid, 'u._id': uid }, { projection: subscriptionFields }) + : Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); -export async function notifyOnSubscriptionChangedByUserPreferences( - uid: ISubscription['u']['_id'], - notificationOriginField: keyof ISubscription, - originFieldNotEqualValue: 'user' | 'subscription', - clientAction: Exclude = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } - - const subscriptions = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, { - projection: subscriptionFields, - }); - - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} - -export async function notifyOnSubscriptionChangedByRoomId( - rid: ISubscription['rid'], - clientAction: ClientAction = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } + if (!subscriptions) { + return; + } - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ rid }, { projection: subscriptionFields }) - : Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); - if (!subscriptions) { - return; - } +export const notifyOnSubscriptionChangedById = withDbWatcherCheck( + async (id: ISubscription['_id'], clientAction: Exclude = 'updated'): Promise => { + const subscription = await Subscriptions.findOneById(id); + if (!subscription) { + return; + } - for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} + }, +); -export async function notifyOnSubscriptionChangedByAutoTranslateAndUserId( - uid: ISubscription['u']['_id'], - clientAction: Exclude = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } +export const notifyOnSubscriptionChangedByUserPreferences = withDbWatcherCheck( + async ( + uid: ISubscription['u']['_id'], + notificationOriginField: keyof ISubscription, + originFieldNotEqualValue: 'user' | 'subscription', + clientAction: Exclude = 'updated', + ): Promise => { + const subscriptions = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, { + projection: subscriptionFields, + }); - const subscriptions = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields }); + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} +export const notifyOnSubscriptionChangedByRoomId = withDbWatcherCheck( + async (rid: ISubscription['rid'], clientAction: ClientAction = 'updated'): Promise => { + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ rid }, { projection: subscriptionFields }) + : Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); -export async function notifyOnSubscriptionChangedByUserIdAndRoomType( - uid: ISubscription['u']['_id'], - t: ISubscription['t'], - clientAction: Exclude = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } + if (!subscriptions) { + return; + } - const subscriptions = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} +export const notifyOnSubscriptionChangedByAutoTranslateAndUserId = withDbWatcherCheck( + async (uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated'): Promise => { + const subscriptions = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields }); -export async function notifyOnSubscriptionChangedByNameAndRoomType( - filter: Partial>, - clientAction: Exclude = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); - const subscriptions = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields }); +export const notifyOnSubscriptionChangedByUserIdAndRoomType = withDbWatcherCheck( + async ( + uid: ISubscription['u']['_id'], + t: ISubscription['t'], + clientAction: Exclude = 'updated', + ): Promise => { + const subscriptions = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); -export async function notifyOnSubscriptionChangedByUserId( - uid: ISubscription['u']['_id'], - clientAction: ClientAction = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } +export const notifyOnSubscriptionChangedByNameAndRoomType = withDbWatcherCheck( + async (filter: Partial>, clientAction: Exclude = 'updated'): Promise => { + const subscriptions = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields }); - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ 'u._id': uid }, { projection: subscriptionFields }) - : Subscriptions.findByUserId(uid, { projection: subscriptionFields }); + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); - if (!subscriptions) { - return; - } +export const notifyOnSubscriptionChangedByUserId = withDbWatcherCheck( + async (uid: ISubscription['u']['_id'], clientAction: ClientAction = 'updated'): Promise => { + const subscriptions = + clientAction === 'removed' + ? Subscriptions.trashFind({ 'u._id': uid }, { projection: subscriptionFields }) + : Subscriptions.findByUserId(uid, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} + if (!subscriptions) { + return; + } -export async function notifyOnSubscriptionChangedByRoomIdAndUserIds( - rid: ISubscription['rid'], - uids: ISubscription['u']['_id'][], - clientAction: Exclude = 'updated', -): Promise { - if (!dbWatchersDisabled) { - return; - } + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); - const subscriptions = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); +export const notifyOnSubscriptionChangedByRoomIdAndUserIds = withDbWatcherCheck( + async ( + rid: ISubscription['rid'], + uids: ISubscription['u']['_id'][], + clientAction: Exclude = 'updated', + ): Promise => { + const subscriptions = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { - void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } -} + for await (const subscription of subscriptions) { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + } + }, +); From a90822d6d8d1611a602dd46a50272cebe1427e81 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 19 Aug 2024 18:38:31 -0300 Subject: [PATCH 29/38] revert receipts change --- .../server/hooks/afterSaveMessage.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts index 62623b1a4a1c..f5f3e998838a 100644 --- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts +++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts @@ -1,4 +1,5 @@ -import { isEditedMessage } from '@rocket.chat/core-typings'; +import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../../lib/callbacks'; import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt'; @@ -11,6 +12,12 @@ callbacks.add( return message; } + // TODO this should be removed + if (!isOmnichannelRoom(room) || !room.closedAt) { + // set subscription as read right after message was sent + await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id); + } + // mark message as read as well await ReadReceipt.markMessageAsReadBySender(message, room, message.u._id); From 33ec6b5f35f6f6b502962c462e803a1d46bcf850 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Aug 2024 11:33:56 -0300 Subject: [PATCH 30/38] fix read receipts --- .../app/lib/server/lib/notifyUsersOnMessage.ts | 14 +++++++++++++- .../server/hooks/afterSaveMessage.ts | 9 +-------- apps/meteor/server/models/raw/Subscriptions.ts | 2 +- .../src/models/ISubscriptionsModel.ts | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 2ce72854ad55..7551cabb6e63 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -7,7 +7,11 @@ import moment from 'moment'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; -import { notifyOnSubscriptionChanged, notifyOnSubscriptionChangedByRoomIdAndUserIds } from './notifyListener'; +import { + notifyOnSubscriptionChanged, + notifyOnSubscriptionChangedByRoomIdAndUserId, + notifyOnSubscriptionChangedByRoomIdAndUserIds, +} from './notifyListener'; function messageContainsHighlight(message: IMessage, highlights: string[]): boolean { if (!highlights || highlights.length === 0) return false; @@ -135,10 +139,18 @@ async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); } + // update subscriptions of other members of the room await Promise.all([ Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id), Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id), ]); + + // update subscription of the message sender + await Subscriptions.setAsReadByRoomIdAndUserId(message.rid, message.u._id); + const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(message.rid, message.u._id); + if (setAsReadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(message.rid, message.u._id); + } } export async function updateThreadUsersSubscriptions(message: IMessage, replies: IUser['_id'][]): Promise { diff --git a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts index f5f3e998838a..62623b1a4a1c 100644 --- a/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts +++ b/apps/meteor/ee/app/message-read-receipt/server/hooks/afterSaveMessage.ts @@ -1,5 +1,4 @@ -import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { Subscriptions } from '@rocket.chat/models'; +import { isEditedMessage } from '@rocket.chat/core-typings'; import { callbacks } from '../../../../../lib/callbacks'; import { ReadReceipt } from '../../../../server/lib/message-read-receipt/ReadReceipt'; @@ -12,12 +11,6 @@ callbacks.add( return message; } - // TODO this should be removed - if (!isOmnichannelRoom(room) || !room.closedAt) { - // set subscription as read right after message was sent - await Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id); - } - // mark message as read as well await ReadReceipt.markMessageAsReadBySender(message, room, message.u._id); diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts index a66e597f13f6..3eef8d720b2f 100644 --- a/apps/meteor/server/models/raw/Subscriptions.ts +++ b/apps/meteor/server/models/raw/Subscriptions.ts @@ -191,7 +191,7 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri readThreads = false, alert = false, options: FindOptions = {}, - ): ReturnType['update']> { + ): ReturnType['updateOne']> { const query: Filter = { rid, 'u._id': uid, diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index bbc25c162794..d7ef6a136533 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -41,7 +41,7 @@ export interface ISubscriptionsModel extends IBaseModel { readThreads?: boolean, alert?: boolean, options?: FindOptions, - ): ReturnType['update']>; + ): ReturnType['updateOne']>; removeRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][], rid: IRoom['_id']): Promise; From 620acd5558556d46795b6daa9f8fe9704332986b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Aug 2024 11:57:24 -0300 Subject: [PATCH 31/38] do not notify on user delete --- apps/meteor/app/lib/server/functions/deleteUser.ts | 6 +----- apps/meteor/app/lib/server/lib/notifyListener.ts | 11 ++--------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index 8136f31946e1..483085d40811 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -25,7 +25,6 @@ import { notifyOnIntegrationChangedByUserId, notifyOnLivechatDepartmentAgentChanged, notifyOnUserChange, - notifyOnSubscriptionChangedByUserId, } from '../lib/notifyListener'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; @@ -113,10 +112,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele const rids = subscribedRooms.map((room) => room.rid); void notifyOnRoomChangedById(rids); - const deletedCount = await Subscriptions.removeByUserId(userId); - if (deletedCount) { - void notifyOnSubscriptionChangedByUserId(userId, 'removed'); - } + await Subscriptions.removeByUserId(userId); // Remove user as livechat agent if (user.roles.includes('livechat-agent')) { diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 865e8f0a56ca..4fe210e38d0f 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -574,15 +574,8 @@ export const notifyOnSubscriptionChangedByNameAndRoomType = withDbWatcherCheck( ); export const notifyOnSubscriptionChangedByUserId = withDbWatcherCheck( - async (uid: ISubscription['u']['_id'], clientAction: ClientAction = 'updated'): Promise => { - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ 'u._id': uid }, { projection: subscriptionFields }) - : Subscriptions.findByUserId(uid, { projection: subscriptionFields }); - - if (!subscriptions) { - return; - } + async (uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated'): Promise => { + const subscriptions = Subscriptions.findByUserId(uid, { projection: subscriptionFields }); for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); From ab8c87a266310b9da47c1a59734ac2230ffb6c05 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Aug 2024 18:14:30 -0300 Subject: [PATCH 32/38] use findOneAndDelete --- .../federation/server/endpoints/dispatch.js | 8 ++--- .../server/functions/removeUserFromRoom.ts | 5 ++- .../app/lib/server/lib/notifyListener.ts | 17 ++++------ apps/meteor/app/livechat/server/lib/Helper.ts | 5 ++- .../server/methods/removeUserFromRoom.ts | 8 +++-- apps/meteor/server/models/dummy/BaseDummy.ts | 7 ++++ apps/meteor/server/models/raw/BaseRaw.ts | 33 ++++++++++++++++++- .../meteor/server/models/raw/Subscriptions.ts | 10 +++--- .../model-typings/src/models/IBaseModel.ts | 2 ++ .../src/models/ISubscriptionsModel.ts | 2 +- 10 files changed, 66 insertions(+), 31 deletions(-) diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index 26591b5b1e76..4f2a197b25ee 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -10,8 +10,8 @@ import { notifyOnMessageChange, notifyOnRoomChanged, notifyOnRoomChangedById, + notifyOnSubscriptionChanged, notifyOnSubscriptionChangedById, - notifyOnSubscriptionChangedByRoomIdAndUserId, } from '../../../lib/server/lib/notifyListener'; import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage'; import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage'; @@ -186,9 +186,8 @@ const eventHandlers = { // Remove the user's subscription const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); - if (deletedSubscription) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(roomId, user._id, 'removed'); + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); } // Refresh the servers list @@ -218,9 +217,8 @@ const eventHandlers = { // Remove the user's subscription const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); - if (deletedSubscription) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(roomId, user._id, 'removed'); + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); } // Refresh the servers list diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts index d9455b494437..861d641f64e0 100644 --- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts +++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts @@ -8,7 +8,7 @@ import { Meteor } from 'meteor/meteor'; import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback'; import { beforeLeaveRoomCallback } from '../../../../lib/callbacks/beforeLeaveRoomCallback'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener'; export const removeUserFromRoom = async function (rid: string, user: IUser, options?: { byUser: IUser }): Promise { const room = await Rooms.findOneById(rid); @@ -57,9 +57,8 @@ export const removeUserFromRoom = async function (rid: string, user: IUser, opti } const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, user._id); - if (deletedSubscription) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, user._id, 'removed'); + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); } if (room.teamId && room.teamMain) { diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 4fe210e38d0f..0df50db7d4eb 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -472,21 +472,18 @@ export const notifyOnMessageChange = withDbWatcherCheck(async ({ id, data }: { i }); export const notifyOnSubscriptionChanged = withDbWatcherCheck( - async (subscription: ISubscription, clientAction: Exclude = 'updated'): Promise => { + async (subscription: ISubscription, clientAction: ClientAction = 'updated'): Promise => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); }, ); export const notifyOnSubscriptionChangedByRoomIdAndUserId = withDbWatcherCheck( - async (rid: ISubscription['rid'], uid: ISubscription['u']['_id'], clientAction: ClientAction = 'updated'): Promise => { - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ rid, 'u._id': uid }, { projection: subscriptionFields }) - : Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); - - if (!subscriptions) { - return; - } + async ( + rid: ISubscription['rid'], + uid: ISubscription['u']['_id'], + clientAction: Exclude = 'updated', + ): Promise => { + const subscriptions = Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index b703713cec0c..17f21d8d7b04 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -41,7 +41,7 @@ import { notifyOnLivechatDepartmentAgentChangedByAgentsAndDepartmentId, notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomId, - notifyOnSubscriptionChangedByRoomIdAndUserId, + notifyOnSubscriptionChanged, } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { Livechat as LivechatTyped } from './LivechatTyped'; @@ -306,9 +306,8 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username } } const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, _id); - if (deletedSubscription) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, _id, 'removed'); + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); } await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name }); diff --git a/apps/meteor/server/methods/removeUserFromRoom.ts b/apps/meteor/server/methods/removeUserFromRoom.ts index 80a978dea284..e11ac6d5651e 100644 --- a/apps/meteor/server/methods/removeUserFromRoom.ts +++ b/apps/meteor/server/methods/removeUserFromRoom.ts @@ -9,7 +9,10 @@ import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync, getUsersInRole } from '../../app/authorization/server'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../app/authorization/server/functions/hasRole'; -import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; +import { + notifyOnRoomChanged, + notifyOnSubscriptionChanged, +} from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; import { RoomMemberActions } from '../../definition/IRoomTypeConfig'; import { callbacks } from '../../lib/callbacks'; @@ -90,9 +93,8 @@ export const removeUserFromRoomMethod = async (fromId: string, data: { rid: stri await callbacks.run('beforeRemoveFromRoom', { removedUser, userWhoRemoved: fromUser }, room); const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id); - if (deletedSubscription) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(data.rid, removedUser._id, 'removed'); + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); } if (['c', 'p'].includes(room.t) === true) { diff --git a/apps/meteor/server/models/dummy/BaseDummy.ts b/apps/meteor/server/models/dummy/BaseDummy.ts index c3052ede9487..049295c1a28a 100644 --- a/apps/meteor/server/models/dummy/BaseDummy.ts +++ b/apps/meteor/server/models/dummy/BaseDummy.ts @@ -53,6 +53,13 @@ export class BaseDummy< return this.collectionName; } + async findOneAndDelete(): Promise> { + return { + value: null, + ok: 1, + }; + } + async findOneAndUpdate(): Promise> { return { value: null, diff --git a/apps/meteor/server/models/raw/BaseRaw.ts b/apps/meteor/server/models/raw/BaseRaw.ts index 1a3dd1a3eb4c..525e8095ecd0 100644 --- a/apps/meteor/server/models/raw/BaseRaw.ts +++ b/apps/meteor/server/models/raw/BaseRaw.ts @@ -26,6 +26,7 @@ import type { InsertOneResult, DeleteResult, DeleteOptions, + FindOneAndDeleteOptions, } from 'mongodb'; import { setUpdatedAt } from './setUpdatedAt'; @@ -315,7 +316,37 @@ export abstract class BaseRaw< return this.col.deleteOne(filter); } - async deleteMany(filter: Filter, options?: DeleteOptions): Promise { + async findOneAndDelete(filter: Filter, options?: FindOneAndDeleteOptions): Promise> { + if (!this.trash) { + if (options) { + return this.col.findOneAndDelete(filter, options); + } + return this.col.findOneAndDelete(filter); + } + + const result = await this.col.findOneAndDelete(filter); + + const { value: doc } = result; + if (!doc) { + return result; + } + + const { _id, ...record } = doc; + + const trash: TDeleted = { + ...record, + _deletedAt: new Date(), + __collection__: this.name, + } as unknown as TDeleted; + + // since the operation is not atomic, we need to make sure that the record is not already deleted/inserted + await this.trash?.updateOne({ _id } as Filter, { $set: trash } as UpdateFilter, { + upsert: true, + }); + + return result; + } + if (!this.trash) { if (options) { return this.col.deleteMany(filter, options); diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts index 3eef8d720b2f..f26113872054 100644 --- a/apps/meteor/server/models/raw/Subscriptions.ts +++ b/apps/meteor/server/models/raw/Subscriptions.ts @@ -1848,21 +1848,21 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return result; } - async removeByRoomIdAndUserId(roomId: string, userId: string): Promise { + async removeByRoomIdAndUserId(roomId: string, userId: string): Promise { const query = { 'rid': roomId, 'u._id': userId, }; - const result = (await this.deleteMany(query)).deletedCount; + const { value: doc } = await this.findOneAndDelete(query); - if (typeof result === 'number' && result > 0) { - await Rooms.incUsersCountById(roomId, -result); + if (doc) { + await Rooms.incUsersCountById(roomId, -1); } await Users.removeRoomByUserId(userId, roomId); - return result; + return doc; } async removeByRoomIds(rids: string[]): Promise { diff --git a/packages/model-typings/src/models/IBaseModel.ts b/packages/model-typings/src/models/IBaseModel.ts index 246c3ae253dd..1bfabcdeeeb0 100644 --- a/packages/model-typings/src/models/IBaseModel.ts +++ b/packages/model-typings/src/models/IBaseModel.ts @@ -9,6 +9,7 @@ import type { EnhancedOmit, Filter, FindCursor, + FindOneAndDeleteOptions, FindOneAndUpdateOptions, FindOptions, InsertManyResult, @@ -53,6 +54,7 @@ export interface IBaseModel< getUpdater(): Updater; updateFromUpdater(query: Filter, updater: Updater): Promise; + findOneAndDelete(filter: Filter, options?: FindOneAndDeleteOptions): Promise>; findOneAndUpdate(query: Filter, update: UpdateFilter | T, options?: FindOneAndUpdateOptions): Promise>; findOneById(_id: T['_id'], options?: FindOptions | undefined): Promise; diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index d7ef6a136533..5a664fba8f9d 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -283,7 +283,7 @@ export interface ISubscriptionsModel extends IBaseModel { users: { user: AtLeast; extraData: Record }[], ): Promise>; removeByRoomIdsAndUserId(rids: string[], userId: string): Promise; - removeByRoomIdAndUserId(roomId: string, userId: string): Promise; + removeByRoomIdAndUserId(roomId: string, userId: string): Promise; removeByRoomIds(rids: string[]): Promise; From 0d26bfe25a5e82326ea4eebe5b71f099d6199e11 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Aug 2024 10:33:52 -0300 Subject: [PATCH 33/38] implement onTrash --- .../lib/server/functions/closeLivechatRoom.ts | 11 +++---- .../app/lib/server/functions/deleteRoom.ts | 8 +++-- .../functions/relinquishRoomOwnerships.ts | 16 +++++----- .../app/lib/server/lib/notifyListener.ts | 11 ++----- .../app/livechat/server/lib/LivechatTyped.ts | 29 +++++++++++-------- apps/meteor/server/models/raw/BaseRaw.ts | 3 ++ .../meteor/server/models/raw/Subscriptions.ts | 8 ++--- .../rocket-chat/adapters/Room.ts | 13 +++++---- .../model-typings/src/models/IBaseModel.ts | 2 +- .../src/models/ISubscriptionsModel.ts | 4 +-- 10 files changed, 55 insertions(+), 50 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts index b716be044d57..44d3249e4450 100644 --- a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts +++ b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts @@ -5,6 +5,7 @@ import { LivechatRooms, Subscriptions } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import type { CloseRoomParams } from '../../../livechat/server/lib/LivechatTyped'; import { Livechat } from '../../../livechat/server/lib/LivechatTyped'; +import { notifyOnSubscriptionChanged } from '../lib/notifyListener'; export const closeLivechatRoom = async ( user: IUser, @@ -34,11 +35,11 @@ export const closeLivechatRoom = async ( } if (!room.open) { - const subscriptionsLeft = await Subscriptions.countByRoomId(roomId); - if (subscriptionsLeft) { - await Subscriptions.removeByRoomId(roomId); - return; - } + await Subscriptions.removeByRoomId(roomId, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }); throw new Error('error-room-already-closed'); } diff --git a/apps/meteor/app/lib/server/functions/deleteRoom.ts b/apps/meteor/app/lib/server/functions/deleteRoom.ts index 2ea8bd38c320..36938d3d9eb5 100644 --- a/apps/meteor/app/lib/server/functions/deleteRoom.ts +++ b/apps/meteor/app/lib/server/functions/deleteRoom.ts @@ -2,7 +2,7 @@ import { Messages, Rooms, Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener'; export const deleteRoom = async function (rid: string): Promise { await FileUpload.removeFilesByRoomId(rid); @@ -11,7 +11,11 @@ export const deleteRoom = async function (rid: string): Promise { await callbacks.run('beforeDeleteRoom', rid); - (await Subscriptions.removeByRoomId(rid)).deletedCount && void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); + await Subscriptions.removeByRoomId(rid, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }); await FileUpload.getStore('Avatars').deleteByRoomId(rid); diff --git a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts index 0ff082318838..bdf1ce05c657 100644 --- a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts +++ b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts @@ -1,24 +1,22 @@ import { Messages, Roles, Rooms, Subscriptions, ReadReceipts } from '@rocket.chat/models'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; +import { notifyOnSubscriptionChanged } from '../lib/notifyListener'; import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; const bulkRoomCleanUp = async (rids: string[]): Promise => { - const responses = await Promise.all([ - Subscriptions.removeByRoomIds(rids), + await Promise.all([ + Subscriptions.removeByRoomIds(rids, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }), Messages.removeByRoomIds(rids), ReadReceipts.removeByRoomIds(rids), Rooms.removeByIds(rids), // no bulk deletion for files ...rids.map((rid) => FileUpload.removeFilesByRoomId(rid)), ]); - - if (responses[0].deletedCount) { - for await (const rid of rids) { - await notifyOnSubscriptionChangedByRoomId(rid); - } - } }; export const relinquishRoomOwnerships = async function ( diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 0df50db7d4eb..2bec86a0d403 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -520,15 +520,8 @@ export const notifyOnSubscriptionChangedByUserPreferences = withDbWatcherCheck( ); export const notifyOnSubscriptionChangedByRoomId = withDbWatcherCheck( - async (rid: ISubscription['rid'], clientAction: ClientAction = 'updated'): Promise => { - const subscriptions = - clientAction === 'removed' - ? Subscriptions.trashFind({ rid }, { projection: subscriptionFields }) - : Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); - - if (!subscriptions) { - return; - } + async (rid: ISubscription['rid'], clientAction: Exclude = 'updated'): Promise => { + const subscriptions = Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); for await (const subscription of subscriptions) { void api.broadcast('watch.subscriptions', { clientAction, subscription }); diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 4fc397eee671..2745b37d1501 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -63,6 +63,7 @@ import { notifyOnUserChange, notifyOnLivechatDepartmentAgentChangedByDepartmentId, notifyOnSubscriptionChangedByRoomId, + notifyOnSubscriptionChanged, } from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; @@ -290,7 +291,11 @@ class LivechatClass { throw new Error('Error closing room'); } - (await Subscriptions.removeByRoomId(rid)).deletedCount && void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); + await Subscriptions.removeByRoomId(rid, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }); this.logger.debug(`DB updated for room ${room._id}`); @@ -516,15 +521,15 @@ class LivechatClass { const result = await Promise.allSettled([ Messages.removeByRoomId(rid), ReadReceipts.removeByRoomId(rid), - Subscriptions.removeByRoomId(rid), + Subscriptions.removeByRoomId(rid, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }), LivechatInquiry.removeByRoomId(rid), LivechatRooms.removeById(rid), ]); - if (result[2]?.status === 'fulfilled' && result[2].value?.deletedCount) { - void notifyOnSubscriptionChangedByRoomId(rid, 'removed'); - } - if (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount && inquiry) { void notifyOnLivechatInquiryChanged(inquiry, 'removed'); } @@ -1147,16 +1152,16 @@ class LivechatClass { const cursor = LivechatRooms.findByVisitorToken(token); for await (const room of cursor) { - const [{ deletedCount }] = await Promise.all([ - Subscriptions.removeByRoomId(room._id), + await Promise.all([ + Subscriptions.removeByRoomId(room._id, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }), FileUpload.removeFilesByRoomId(room._id), Messages.removeByRoomId(room._id), ReadReceipts.removeByRoomId(room._id), ]); - - if (deletedCount) { - void notifyOnSubscriptionChangedByRoomId(room._id, 'removed'); - } } await LivechatRooms.removeByVisitorToken(token); diff --git a/apps/meteor/server/models/raw/BaseRaw.ts b/apps/meteor/server/models/raw/BaseRaw.ts index 525e8095ecd0..fae43f2dbfb0 100644 --- a/apps/meteor/server/models/raw/BaseRaw.ts +++ b/apps/meteor/server/models/raw/BaseRaw.ts @@ -347,6 +347,7 @@ export abstract class BaseRaw< return result; } + async deleteMany(filter: Filter, options?: DeleteOptions & { onTrash?: (record: ResultFields) => void }): Promise { if (!this.trash) { if (options) { return this.col.deleteMany(filter, options); @@ -372,6 +373,8 @@ export abstract class BaseRaw< await this.trash?.updateOne({ _id } as Filter, { $set: trash } as UpdateFilter, { upsert: true, }); + + void options?.onTrash?.(doc); } if (options) { diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts index f26113872054..efb75ed3d17f 100644 --- a/apps/meteor/server/models/raw/Subscriptions.ts +++ b/apps/meteor/server/models/raw/Subscriptions.ts @@ -354,12 +354,12 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return this.find(query, options || {}); } - async removeByRoomId(roomId: ISubscription['rid']): Promise { + async removeByRoomId(roomId: ISubscription['rid'], options?: { onTrash: (doc: ISubscription) => void }): Promise { const query = { rid: roomId, }; - const deleteResult = await this.deleteMany(query); + const deleteResult = await this.deleteMany(query, options); if (deleteResult?.deletedCount) { await Rooms.incUsersCountByIds([roomId], -deleteResult.deletedCount); @@ -1865,8 +1865,8 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri return doc; } - async removeByRoomIds(rids: string[]): Promise { - const result = await this.deleteMany({ rid: { $in: rids } }); + async removeByRoomIds(rids: string[], options?: { onTrash: (doc: ISubscription) => void }): Promise { + const result = await this.deleteMany({ rid: { $in: rids } }, options); await Users.removeRoomByRoomIds(rids); diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts index 32f939d81c56..494676841621 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts @@ -8,6 +8,7 @@ import { addUserToRoom } from '../../../../../../app/lib/server/functions/addUse import { createRoom } from '../../../../../../app/lib/server/functions/createRoom'; import { removeUserFromRoom } from '../../../../../../app/lib/server/functions/removeUserFromRoom'; import { + notifyOnSubscriptionChanged, notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomId, notifyOnSubscriptionChangedByRoomIdAndUserId, @@ -84,15 +85,15 @@ export class RocketChatRoomAdapter { public async removeDirectMessageRoom(federatedRoom: FederatedRoom): Promise { const roomId = federatedRoom.getInternalId(); - const responses = await Promise.all([ + await Promise.all([ Rooms.removeById(roomId), - Subscriptions.removeByRoomId(roomId), + Subscriptions.removeByRoomId(roomId, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + } + }), MatrixBridgedRoom.removeByLocalRoomId(roomId), ]); - - if (responses[0].deletedCount) { - void notifyOnSubscriptionChangedByRoomId(roomId, 'removed'); - } } public async createFederatedRoomForDirectMessage(federatedRoom: DirectMessageFederatedRoom): Promise { diff --git a/packages/model-typings/src/models/IBaseModel.ts b/packages/model-typings/src/models/IBaseModel.ts index 1bfabcdeeeb0..626f91385a04 100644 --- a/packages/model-typings/src/models/IBaseModel.ts +++ b/packages/model-typings/src/models/IBaseModel.ts @@ -95,7 +95,7 @@ export interface IBaseModel< deleteOne(filter: Filter, options?: DeleteOptions & { bypassDocumentValidation?: boolean }): Promise; - deleteMany(filter: Filter, options?: DeleteOptions): Promise; + deleteMany(filter: Filter, options?: DeleteOptions & { onTrash?: (record: ResultFields) => void }): Promise; // Trash trashFind

( diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts index 5a664fba8f9d..3703996898f6 100644 --- a/packages/model-typings/src/models/ISubscriptionsModel.ts +++ b/packages/model-typings/src/models/ISubscriptionsModel.ts @@ -83,7 +83,7 @@ export interface ISubscriptionsModel extends IBaseModel { options?: FindOptions, ): FindCursor; - removeByRoomId(roomId: ISubscription['rid']): Promise; + removeByRoomId(roomId: ISubscription['rid'], options?: { onTrash: (doc: ISubscription) => void }): Promise; findByRoomIdExcludingUserIds( roomId: ISubscription['rid'], @@ -285,7 +285,7 @@ export interface ISubscriptionsModel extends IBaseModel { removeByRoomIdsAndUserId(rids: string[], userId: string): Promise; removeByRoomIdAndUserId(roomId: string, userId: string): Promise; - removeByRoomIds(rids: string[]): Promise; + removeByRoomIds(rids: string[], options?: { onTrash: (doc: ISubscription) => void }): Promise; addUnreadThreadByRoomIdAndUserIds( rid: string, From e9f39f1a82bfd1ee60ecb601e804f2d9684740e8 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Aug 2024 11:43:51 -0300 Subject: [PATCH 34/38] fix unit tests --- .../app/lib/server/functions/closeLivechatRoom.ts | 5 ++++- .../server/functions/closeLivechatRoom.tests.ts | 14 ++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts index 44d3249e4450..263b137ae00c 100644 --- a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts +++ b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts @@ -35,11 +35,14 @@ export const closeLivechatRoom = async ( } if (!room.open) { - await Subscriptions.removeByRoomId(roomId, { + const { deletedCount } = await Subscriptions.removeByRoomId(roomId, { async onTrash(doc) { void notifyOnSubscriptionChanged(doc, 'removed'); }, }); + if (deletedCount) { + return; + } throw new Error('error-room-already-closed'); } diff --git a/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts b/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts index 07ee437832d2..b40b971128bc 100644 --- a/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts +++ b/apps/meteor/tests/unit/app/lib/server/functions/closeLivechatRoom.tests.ts @@ -73,7 +73,7 @@ describe('closeLivechatRoom', () => { it('should not perform any operation when a closed room with no subscriptions is provided and the caller is not subscribed to it', async () => { livechatRoomsStub.findOneById.resolves({ ...room, open: false }); - subscriptionsStub.countByRoomId.resolves(0); + subscriptionsStub.removeByRoomId.resolves({ deletedCount: 0 }); subscriptionsStub.findOneByRoomIdAndUserId.resolves(null); hasPermissionStub.resolves(true); @@ -81,13 +81,12 @@ describe('closeLivechatRoom', () => { expect(livechatStub.closeRoom.notCalled).to.be.true; expect(livechatRoomsStub.findOneById.calledOnceWith(room._id)).to.be.true; expect(subscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.be.true; - expect(subscriptionsStub.countByRoomId.calledOnceWith(room._id)).to.be.true; - expect(subscriptionsStub.removeByRoomId.notCalled).to.be.true; + expect(subscriptionsStub.removeByRoomId.calledOnceWith(room._id)).to.be.true; }); it('should remove dangling subscription when a closed room with subscriptions is provided and the caller is not subscribed to it', async () => { livechatRoomsStub.findOneById.resolves({ ...room, open: false }); - subscriptionsStub.countByRoomId.resolves(1); + subscriptionsStub.removeByRoomId.resolves({ deletedCount: 1 }); subscriptionsStub.findOneByRoomIdAndUserId.resolves(null); hasPermissionStub.resolves(true); @@ -95,28 +94,25 @@ describe('closeLivechatRoom', () => { expect(livechatStub.closeRoom.notCalled).to.be.true; expect(livechatRoomsStub.findOneById.calledOnceWith(room._id)).to.be.true; expect(subscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.be.true; - expect(subscriptionsStub.countByRoomId.calledOnceWith(room._id)).to.be.true; expect(subscriptionsStub.removeByRoomId.calledOnceWith(room._id)).to.be.true; }); it('should remove dangling subscription when a closed room is provided but the user is still subscribed to it', async () => { livechatRoomsStub.findOneById.resolves({ ...room, open: false }); subscriptionsStub.findOneByRoomIdAndUserId.resolves(subscription); - subscriptionsStub.countByRoomId.resolves(1); + subscriptionsStub.removeByRoomId.resolves({ deletedCount: 1 }); hasPermissionStub.resolves(true); await closeLivechatRoom(user, room._id, {}); expect(livechatStub.closeRoom.notCalled).to.be.true; expect(livechatRoomsStub.findOneById.calledOnceWith(room._id)).to.be.true; expect(subscriptionsStub.findOneByRoomIdAndUserId.notCalled).to.be.true; - expect(subscriptionsStub.countByRoomId.calledOnceWith(room._id)).to.be.true; expect(subscriptionsStub.removeByRoomId.calledOnceWith(room._id)).to.be.true; }); it('should not perform any operation when the caller is not subscribed to an open room and does not have the permission to close others rooms', async () => { livechatRoomsStub.findOneById.resolves(room); subscriptionsStub.findOneByRoomIdAndUserId.resolves(null); - subscriptionsStub.countByRoomId.resolves(1); hasPermissionStub.resolves(false); await expect(closeLivechatRoom(user, room._id, {})).to.be.rejectedWith('error-not-authorized'); @@ -129,7 +125,6 @@ describe('closeLivechatRoom', () => { it('should close the room when the caller is not subscribed to it but has the permission to close others rooms', async () => { livechatRoomsStub.findOneById.resolves(room); subscriptionsStub.findOneByRoomIdAndUserId.resolves(null); - subscriptionsStub.countByRoomId.resolves(1); hasPermissionStub.resolves(true); await closeLivechatRoom(user, room._id, {}); @@ -142,7 +137,6 @@ describe('closeLivechatRoom', () => { it('should close the room when the caller is subscribed to it and does not have the permission to close others rooms', async () => { livechatRoomsStub.findOneById.resolves(room); subscriptionsStub.findOneByRoomIdAndUserId.resolves(subscription); - subscriptionsStub.countByRoomId.resolves(1); hasPermissionStub.resolves(false); await closeLivechatRoom(user, room._id, {}); From 089aba2fc14aa6c8557571754378d63c9e422923 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Aug 2024 11:45:16 -0300 Subject: [PATCH 35/38] fix lint --- apps/meteor/server/methods/removeUserFromRoom.ts | 5 +---- .../federation/infrastructure/rocket-chat/adapters/Room.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/meteor/server/methods/removeUserFromRoom.ts b/apps/meteor/server/methods/removeUserFromRoom.ts index 3ba0f2554ad5..781ffe3a2671 100644 --- a/apps/meteor/server/methods/removeUserFromRoom.ts +++ b/apps/meteor/server/methods/removeUserFromRoom.ts @@ -9,10 +9,7 @@ import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync, getUsersInRole } from '../../app/authorization/server'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../app/authorization/server/functions/hasRole'; -import { - notifyOnRoomChanged, - notifyOnSubscriptionChanged, -} from '../../app/lib/server/lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChanged } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; import { RoomMemberActions } from '../../definition/IRoomTypeConfig'; import { callbacks } from '../../lib/callbacks'; diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts index 494676841621..9deabb53006e 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts @@ -90,7 +90,7 @@ export class RocketChatRoomAdapter { Subscriptions.removeByRoomId(roomId, { async onTrash(doc) { void notifyOnSubscriptionChanged(doc, 'removed'); - } + }, }), MatrixBridgedRoom.removeByLocalRoomId(roomId), ]); From 8bdde3c4131bcdd32362ac909469b14371cb2470 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Aug 2024 18:24:05 -0300 Subject: [PATCH 36/38] code review --- .../server/functions/saveRoomCustomFields.ts | 8 ++++---- .../server/functions/saveRoomEncrypted.ts | 4 ++-- .../e2e/server/functions/handleSuggestedGroupKey.ts | 7 ++++--- .../app/lib/server/functions/cleanRoomHistory.ts | 11 ++++------- apps/meteor/app/lib/server/functions/createRoom.ts | 6 +----- apps/meteor/app/lib/server/functions/deleteRoom.ts | 5 ++++- .../lib/server/functions/relinquishRoomOwnerships.ts | 7 ++++--- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts index de2cf8139af1..ef70ff65c067 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts @@ -18,13 +18,13 @@ export const saveRoomCustomFields = async function (rid: string, roomCustomField }); } - const response = await Rooms.setCustomFieldsById(rid, roomCustomFields); + const ret = await Rooms.setCustomFieldsById(rid, roomCustomFields); // Update customFields of any user's Subscription related with this rid - const updateCustomFields = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields); - if (updateCustomFields.modifiedCount) { + const { modifiedCount } = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields); + if (modifiedCount) { void notifyOnSubscriptionChangedByRoomId(rid); } - return response; + return ret; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts index cb9da9be5840..c1a441463a98 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts @@ -29,8 +29,8 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean } if (encrypted) { - const disableAutoTranslateResponse = await Subscriptions.disableAutoTranslateByRoomId(rid); - if (disableAutoTranslateResponse.modifiedCount) { + const { modifiedCount } = await Subscriptions.disableAutoTranslateByRoomId(rid); + if (modifiedCount) { void notifyOnSubscriptionChangedByRoomId(rid); } } diff --git a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts index a9af65d219de..22eccf03f407 100644 --- a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts +++ b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts @@ -32,7 +32,8 @@ export async function handleSuggestedGroupKey( await Rooms.addUserIdToE2EEQueueByRoomIds([sub.rid], userId); } - await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); - - void notifyOnSubscriptionChangedById(sub._id); + const { modifiedCount } = await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); + if (modifiedCount) { + void notifyOnSubscriptionChangedById(sub._id); + } } diff --git a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts index 8066768849f5..765a03cad87b 100644 --- a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts +++ b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts @@ -86,14 +86,11 @@ export async function cleanRoomHistory({ if (threads.size > 0) { const subscriptionIds: string[] = ( await Subscriptions.findUnreadThreadsByRoomId(rid, [...threads], { projection: { _id: 1 } }).toArray() - ).map(({ _id }: { _id: string }) => _id); + ).map(({ _id }) => _id); - const removedUnreadThreadsResponse = await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); - - if (removedUnreadThreadsResponse.modifiedCount) { - for await (const id of subscriptionIds) { - await notifyOnSubscriptionChangedById(id); - } + const { modifiedCount } = await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); + if (modifiedCount) { + subscriptionIds.forEach((id) => notifyOnSubscriptionChangedById(id)); } } } diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index e397647c671e..769155b66b60 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -104,11 +104,7 @@ async function createUsersSubscriptions({ const { insertedIds } = await Subscriptions.createWithRoomAndManyUsers(room, subs); - if (Object.keys(insertedIds).length) { - for await (const insertedId of Object.values(insertedIds)) { - await notifyOnSubscriptionChangedById(insertedId, 'inserted'); - } - } + Object.values(insertedIds).forEach((subId) => notifyOnSubscriptionChangedById(subId, 'inserted')); await Rooms.incUsersCountById(room._id, subs.length); } diff --git a/apps/meteor/app/lib/server/functions/deleteRoom.ts b/apps/meteor/app/lib/server/functions/deleteRoom.ts index 36938d3d9eb5..386ba8da8b94 100644 --- a/apps/meteor/app/lib/server/functions/deleteRoom.ts +++ b/apps/meteor/app/lib/server/functions/deleteRoom.ts @@ -21,5 +21,8 @@ export const deleteRoom = async function (rid: string): Promise { await callbacks.run('afterDeleteRoom', rid); - (await Rooms.removeById(rid)).deletedCount && void notifyOnRoomChangedById(rid, 'removed'); + const { deletedCount } = await Rooms.removeById(rid); + if (deletedCount) { + void notifyOnRoomChangedById(rid, 'removed'); + } }; diff --git a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts index bdf1ce05c657..3a1d48b0ff1f 100644 --- a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts +++ b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts @@ -5,7 +5,10 @@ import { notifyOnSubscriptionChanged } from '../lib/notifyListener'; import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; const bulkRoomCleanUp = async (rids: string[]): Promise => { - await Promise.all([ + // no bulk deletion for files + await Promise.all(rids.map((rid) => FileUpload.removeFilesByRoomId(rid))); + + return Promise.all([ Subscriptions.removeByRoomIds(rids, { async onTrash(doc) { void notifyOnSubscriptionChanged(doc, 'removed'); @@ -14,8 +17,6 @@ const bulkRoomCleanUp = async (rids: string[]): Promise => { Messages.removeByRoomIds(rids), ReadReceipts.removeByRoomIds(rids), Rooms.removeByIds(rids), - // no bulk deletion for files - ...rids.map((rid) => FileUpload.removeFilesByRoomId(rid)), ]); }; From ed4a171c92933b650ee0085cb8fbdbc24cd41b97 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Aug 2024 18:27:45 -0300 Subject: [PATCH 37/38] use a cursor --- .../app/lib/server/lib/notifyListener.ts | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 2bec86a0d403..778fe89dbbf4 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -483,11 +483,11 @@ export const notifyOnSubscriptionChangedByRoomIdAndUserId = withDbWatcherCheck( uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated', ): Promise => { - const subscriptions = Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); + const cursor = Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); @@ -509,33 +509,33 @@ export const notifyOnSubscriptionChangedByUserPreferences = withDbWatcherCheck( originFieldNotEqualValue: 'user' | 'subscription', clientAction: Exclude = 'updated', ): Promise => { - const subscriptions = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, { + const cursor = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, { projection: subscriptionFields, }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); export const notifyOnSubscriptionChangedByRoomId = withDbWatcherCheck( async (rid: ISubscription['rid'], clientAction: Exclude = 'updated'): Promise => { - const subscriptions = Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); + const cursor = Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); export const notifyOnSubscriptionChangedByAutoTranslateAndUserId = withDbWatcherCheck( async (uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated'): Promise => { - const subscriptions = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields }); + const cursor = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); @@ -545,31 +545,31 @@ export const notifyOnSubscriptionChangedByUserIdAndRoomType = withDbWatcherCheck t: ISubscription['t'], clientAction: Exclude = 'updated', ): Promise => { - const subscriptions = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); + const cursor = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); export const notifyOnSubscriptionChangedByNameAndRoomType = withDbWatcherCheck( async (filter: Partial>, clientAction: Exclude = 'updated'): Promise => { - const subscriptions = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields }); + const cursor = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); export const notifyOnSubscriptionChangedByUserId = withDbWatcherCheck( async (uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated'): Promise => { - const subscriptions = Subscriptions.findByUserId(uid, { projection: subscriptionFields }); + const cursor = Subscriptions.findByUserId(uid, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); @@ -579,10 +579,10 @@ export const notifyOnSubscriptionChangedByRoomIdAndUserIds = withDbWatcherCheck( uids: ISubscription['u']['_id'][], clientAction: Exclude = 'updated', ): Promise => { - const subscriptions = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); + const cursor = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); - for await (const subscription of subscriptions) { + void cursor.forEach((subscription) => { void api.broadcast('watch.subscriptions', { clientAction, subscription }); - } + }); }, ); From 3af69436610d60cb2b0d22a312bb2ff54a8c203e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Aug 2024 19:10:15 -0300 Subject: [PATCH 38/38] revert return type change --- .../meteor/app/lib/server/functions/relinquishRoomOwnerships.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts index 3a1d48b0ff1f..8f1981ca386d 100644 --- a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts +++ b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts @@ -4,7 +4,7 @@ import { FileUpload } from '../../../file-upload/server'; import { notifyOnSubscriptionChanged } from '../lib/notifyListener'; import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; -const bulkRoomCleanUp = async (rids: string[]): Promise => { +const bulkRoomCleanUp = async (rids: string[]): Promise => { // no bulk deletion for files await Promise.all(rids.map((rid) => FileUpload.removeFilesByRoomId(rid)));