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;