diff --git a/apps/meteor/app/apps/server/bridges/rooms.ts b/apps/meteor/app/apps/server/bridges/rooms.ts index 4e9182aaa10..6822d35cefc 100644 --- a/apps/meteor/app/apps/server/bridges/rooms.ts +++ b/apps/meteor/app/apps/server/bridges/rooms.ts @@ -5,10 +5,10 @@ import type { IUser } from '@rocket.chat/apps-engine/definition/users'; import type { IMessage } from '@rocket.chat/apps-engine/definition/messages'; import { Meteor } from 'meteor/meteor'; import type { ISubscription, IUser as ICoreUser } from '@rocket.chat/core-typings'; -import { Subscriptions } from '@rocket.chat/models'; +import { Subscriptions, Rooms } from '@rocket.chat/models'; import type { AppServerOrchestrator } from '../../../../ee/server/apps/orchestrator'; -import { Rooms, Users } from '../../../models/server'; +import { Users } from '../../../models/server'; import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom'; import { deleteRoom } from '../../../lib/server/functions/deleteRoom'; @@ -72,7 +72,7 @@ export class AppRoomBridge extends RoomBridge { protected async getCreatorById(roomId: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is getting the room's creator by id: "${roomId}"`); - const room = Rooms.findOneById(roomId); + const room = await Rooms.findOneById(roomId); if (!room || !room.u || !room.u._id) { return undefined; @@ -84,7 +84,7 @@ export class AppRoomBridge extends RoomBridge { protected async getCreatorByName(roomName: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is getting the room's creator by name: "${roomName}"`); - const room = Rooms.findOneByName(roomName, {}); + const room = await Rooms.findOneByName(roomName, {}); if (!room || !room.u || !room.u._id) { return undefined; @@ -111,13 +111,13 @@ export class AppRoomBridge extends RoomBridge { protected async update(room: IRoom, members: Array = [], appId: string): Promise { this.orch.debugLog(`The App ${appId} is updating a room.`); - if (!room.id || !Rooms.findOneById(room.id)) { + if (!room.id || !(await Rooms.findOneById(room.id))) { throw new Error('A room must exist to update.'); } const rm = await this.orch.getConverters()?.get('rooms').convertAppRoom(room); - Rooms.update(rm._id, rm); + await Rooms.updateOne({ _id: rm._id }, { $set: rm }); for await (const username of members) { const member = Users.findOneByUsername(username, {}); @@ -151,7 +151,7 @@ export class AppRoomBridge extends RoomBridge { rcMessage = this.orch.getConverters()?.get('messages').convertAppMessage(parentMessage); } - if (!rcRoom.prid || !Rooms.findOneById(rcRoom.prid)) { + if (!rcRoom.prid || !(await Rooms.findOneById(rcRoom.prid))) { throw new Error('There must be a parent room to create a discussion.'); } diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomName.js b/apps/meteor/app/channel-settings/server/functions/saveRoomName.js index 80b860e6db2..3751ea497f1 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomName.js +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomName.js @@ -13,7 +13,7 @@ const updateFName = async (rid, displayName) => { }; const updateRoomName = async (rid, displayName) => { - const slugifiedRoomName = getValidRoomName(displayName, rid); + const slugifiedRoomName = await getValidRoomName(displayName, rid); // Check if the username is available if (!(await checkUsernameAvailability(slugifiedRoomName))) { diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts index 950324b17c0..2100f804344 100644 --- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts @@ -21,6 +21,7 @@ import { Users, Rooms, Subscriptions } from '../../../models/server'; import { generateUsernameSuggestion, insertMessage, saveUserIdentity, addUserToDefaultChannels } from '../../../lib/server'; import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus'; import type { Logger } from '../../../../server/lib/logger/Logger'; +import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; type IRoom = Record; type IMessage = Record; @@ -494,10 +495,10 @@ export class ImportDataConverter { return result; } - getMentionedChannelData(importId: string): IMentionedChannel | undefined { + async getMentionedChannelData(importId: string): Promise { // loading the name will also store the id on the cache if it's missing, so this won't run two queries - const name = this.findImportedRoomName(importId); - const _id = this.findImportedRoomId(importId); + const name = await this.findImportedRoomName(importId); + const _id = await this.findImportedRoomId(importId); if (name && _id) { return { @@ -507,8 +508,9 @@ export class ImportDataConverter { } // If the importId was not found, check if we have a room with that name - const room = Rooms.findOneByNonValidatedName(importId, { fields: { name: 1 } }); - if (room) { + const roomName = await getValidRoomName(importId.trim(), undefined, { allowDuplicates: true }); + const room = await RoomsRaw.findOneByNonValidatedName(roomName, { projection: { name: 1 } }); + if (room?.name) { this.addRoomToCache(importId, room._id); this.addRoomNameToCache(importId, room.name); @@ -519,15 +521,15 @@ export class ImportDataConverter { } } - convertMessageChannels(message: IImportMessage): Array | undefined { + async convertMessageChannels(message: IImportMessage): Promise { const { channels } = message; if (!channels) { return; } const result: Array = []; - for (const importId of channels) { - const { name, _id } = this.getMentionedChannelData(importId) || {}; + for await (const importId of channels) { + const { name, _id } = (await this.getMentionedChannelData(importId)) || {}; if (!_id || !name) { this._logger.warn(`Mentioned room not found: ${importId}`); @@ -570,7 +572,7 @@ export class ImportDataConverter { throw new Error('importer-message-unknown-user'); } - const rid = this.findImportedRoomId(data.rid); + const rid = await this.findImportedRoomId(data.rid); if (!rid) { throw new Error('importer-message-unknown-room'); } @@ -580,7 +582,7 @@ export class ImportDataConverter { // Convert the mentions and channels first because these conversions can also modify the msg in the message object const mentions = data.mentions && this.convertMessageMentions(data); - const channels = data.channels && this.convertMessageChannels(data); + const channels = data.channels && (await this.convertMessageChannels(data)); const msgObj: IMessage = { rid, @@ -617,7 +619,7 @@ export class ImportDataConverter { } try { - insertMessage(creator, msgObj, rid, true); + await insertMessage(creator, msgObj, rid, true); } catch (e) { this._logger.warn(`Failed to import message with timestamp ${String(msgObj.ts)} to room ${rid}`); this._logger.error(e); @@ -661,18 +663,18 @@ export class ImportDataConverter { } } - findImportedRoomId(importId: string): string | null { + async findImportedRoomId(importId: string): Promise { if (this._roomCache.has(importId)) { return this._roomCache.get(importId) as string; } const options = { - fields: { + projection: { _id: 1, }, }; - const room = Rooms.findOneByImportId(importId, options); + const room = await RoomsRaw.findOneByImportId(importId, options); if (room) { return this.addRoomToCache(importId, room._id); } @@ -680,24 +682,26 @@ export class ImportDataConverter { return null; } - findImportedRoomName(importId: string): string | undefined { + async findImportedRoomName(importId: string): Promise { if (this._roomNameCache.has(importId)) { return this._roomNameCache.get(importId) as string; } const options = { - fields: { + projection: { _id: 1, name: 1, }, }; - const room = Rooms.findOneByImportId(importId, options); + const room = await RoomsRaw.findOneByImportId(importId, options); if (room) { if (!this._roomCache.has(importId)) { this.addRoomToCache(importId, room._id); } - return this.addRoomNameToCache(importId, room.name); + if (room?.name) { + return this.addRoomNameToCache(importId, room.name); + } } } @@ -874,9 +878,9 @@ export class ImportDataConverter { .filter((user) => user); } - findExistingRoom(data: IImportChannel): IRoom { + async findExistingRoom(data: IImportChannel): Promise { if (data._id && data._id.toUpperCase() === 'GENERAL') { - const room = Rooms.findOneById('GENERAL', {}); + const room = await RoomsRaw.findOneById('GENERAL', {}); // Prevent the importer from trying to create a new general if (!room) { throw new Error('importer-channel-general-not-found'); @@ -891,10 +895,15 @@ export class ImportDataConverter { throw new Error('importer-channel-missing-users'); } - return Rooms.findDirectRoomContainingAllUsernames(users, {}); + return RoomsRaw.findDirectRoomContainingAllUsernames(users, {}); + } + + if (!data.name) { + return null; } - return Rooms.findOneByNonValidatedName(data.name, {}); + const roomName = await getValidRoomName(data.name.trim(), undefined, { allowDuplicates: true }); + return RoomsRaw.findOneByNonValidatedName(roomName, {}); } protected async getChannelsToImport(): Promise> { @@ -921,7 +930,7 @@ export class ImportDataConverter { throw new Error('importer-channel-missing-import-id'); } - const existingRoom = this.findExistingRoom(data); + const existingRoom = await this.findExistingRoom(data); if (existingRoom) { this.updateRoom(existingRoom, data, startedByUserId); diff --git a/apps/meteor/app/irc/server/irc-bridge/peerHandlers/sentMessage.js b/apps/meteor/app/irc/server/irc-bridge/peerHandlers/sentMessage.js index 5fdb37c0ae4..f1eab79eeeb 100644 --- a/apps/meteor/app/irc/server/irc-bridge/peerHandlers/sentMessage.js +++ b/apps/meteor/app/irc/server/irc-bridge/peerHandlers/sentMessage.js @@ -1,4 +1,6 @@ -import { Users, Rooms } from '../../../../models/server'; +import { Rooms } from '@rocket.chat/models'; + +import { Users } from '../../../../models/server'; import { sendMessage, createDirectRoom } from '../../../../lib/server'; /* * @@ -10,7 +12,7 @@ const getDirectRoom = async (source, target) => { const uids = [source._id, target._id]; const { _id, ...extraData } = await createDirectRoom([source, target]); - const room = Rooms.findOneDirectRoomContainingAllUserIDs(uids); + const room = await Rooms.findOneDirectRoomContainingAllUserIDs(uids); if (room) { return { t: 'd', @@ -37,7 +39,7 @@ export default async function handleSentMessage(args) { let room; if (args.roomName) { - room = Rooms.findOneByName(args.roomName); + room = await Rooms.findOneByName(args.roomName); } else { const recipientUser = Users.findOne({ 'profile.irc.nick': args.recipientNick, diff --git a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts index f8359f4d4a7..998aaa17ffc 100644 --- a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts +++ b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts @@ -1,15 +1,14 @@ import type { IUser } from '@rocket.chat/core-typings'; -import { Subscriptions } from '@rocket.chat/models'; +import { Subscriptions, Rooms } from '@rocket.chat/models'; import { Message } from '@rocket.chat/core-services'; -import { Rooms } from '../../../models/server'; import { callbacks } from '../../../../lib/callbacks'; export const addUserToDefaultChannels = async function (user: IUser, silenced?: boolean): Promise { callbacks.run('beforeJoinDefaultChannels', user); - const defaultRooms = Rooms.findByDefaultAndTypes(true, ['c', 'p'], { - fields: { usernames: 0 }, - }).fetch(); + const defaultRooms = await Rooms.findByDefaultAndTypes(true, ['c', 'p'], { + projection: { usernames: 0 }, + }).toArray(); for await (const room of defaultRooms) { if (!(await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 } }))) { // Add a subscription to this user diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index 0782f143366..6ab6fcbd8fe 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -2,11 +2,11 @@ import { AppsEngineException } from '@rocket.chat/apps-engine/definition/excepti import { Meteor } from 'meteor/meteor'; import { Random } from '@rocket.chat/random'; import type { ICreatedRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; -import { Subscriptions } from '@rocket.chat/models'; +import { Subscriptions, Rooms } from '@rocket.chat/models'; import type { MatchKeysAndValues } from 'mongodb'; import type { ISubscriptionExtraData } from '@rocket.chat/core-services'; -import { Users, Rooms } from '../../../models/server'; +import { Users } from '../../../models/server'; import { Apps } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; @@ -71,8 +71,8 @@ export async function createDirectRoom( // Deprecated: using users' _id to compose the room _id is deprecated const room = uids.length === 2 - ? Rooms.findOneById(uids.join(''), { fields: { _id: 1 } }) - : Rooms.findOneDirectRoomContainingAllUserIDs(uids, { fields: { _id: 1 } }); + ? await Rooms.findOneById(uids.join(''), { projection: { _id: 1 } }) + : await Rooms.findOneDirectRoomContainingAllUserIDs(uids, { projection: { _id: 1 } }); const isNewRoom = !room; @@ -114,7 +114,8 @@ export async function createDirectRoom( delete tmpRoom._USERNAMES; } - const rid = room?._id || Rooms.insert(roomInfo); + // @ts-expect-error - TODO: room expects `u` to be passed, but it's not part of the original object in here + const rid = room?._id || (await Rooms.insertOne(roomInfo)).insertedId; if (roomMembers.length === 1) { // dm to yourself @@ -154,7 +155,7 @@ export async function createDirectRoom( // If the room is new, run a callback if (isNewRoom) { - const insertedRoom = Rooms.findOneById(rid); + const insertedRoom = await Rooms.findOneById(rid); callbacks.run('afterCreateDirectRoom', insertedRoom, { members: roomMembers, creatorId: options?.creator }); @@ -162,10 +163,12 @@ export async function createDirectRoom( } return { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ...room!, _id: String(rid), usernames, t: 'd', + rid, inserted: isNewRoom, - ...room, }; } diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 94ffc3fcce6..9d2b3d0f971 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -72,7 +72,7 @@ export const createRoom = async ( fname: name, _updatedAt: now, ...extraData, - name: getValidRoomName(name.trim(), undefined, { + name: await getValidRoomName(name.trim(), undefined, { ...(options?.nameValidationRegex && { nameValidationRegex: options.nameValidationRegex }), }), t: type, diff --git a/apps/meteor/app/lib/server/functions/insertMessage.js b/apps/meteor/app/lib/server/functions/insertMessage.js index 13595390ca8..7c73641f114 100644 --- a/apps/meteor/app/lib/server/functions/insertMessage.js +++ b/apps/meteor/app/lib/server/functions/insertMessage.js @@ -1,8 +1,10 @@ -import { Messages, Rooms } from '../../../models/server'; +import { Rooms } from '@rocket.chat/models'; + +import { Messages } from '../../../models/server'; import { validateMessage, prepareMessageObject } from './sendMessage'; import { parseUrlsInMessage } from './parseUrlsInMessage'; -export const insertMessage = function (user, message, rid, upsert = false) { +export const insertMessage = async function (user, message, rid, upsert = false) { if (!user || !message || !rid) { return false; } @@ -23,12 +25,12 @@ export const insertMessage = function (user, message, rid, upsert = false) { message, ); if (!existingMessage) { - Rooms.incMsgCountById(rid, 1); + await Rooms.incMsgCountById(rid, 1); } message._id = _id; } else { message._id = Messages.insert(message); - Rooms.incMsgCountById(rid, 1); + await Rooms.incMsgCountById(rid, 1); } return message; diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js index 0926907c81e..fd92ce8a851 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.js @@ -1,8 +1,8 @@ import moment from 'moment'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { Subscriptions as SubscriptionsRaw, Rooms as RoomsRaw } from '@rocket.chat/models'; +import { Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models'; -import { Rooms, Subscriptions } from '../../../models/server'; +import { Subscriptions } from '../../../models/server'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; @@ -157,7 +157,7 @@ export async function notifyUsersOnMessage(message, room) { if (message.editedAt) { if (Math.abs(moment(message.editedAt).diff()) > 60000) { // TODO: Review as I am not sure how else to get around this as the incrementing of the msgs count shouldn't be in this callback - Rooms.incMsgCountById(message.rid, 1); + await Rooms.incMsgCountById(message.rid, 1); return message; } @@ -167,25 +167,25 @@ export async function notifyUsersOnMessage(message, room) { (!message.tmid || message.tshow) && (!room.lastMessage || room.lastMessage._id === message._id) ) { - await RoomsRaw.setLastMessageById(message.rid, message); + await Rooms.setLastMessageById(message.rid, message); } return message; } if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) { - Rooms.incMsgCountById(message.rid, 1); + await Rooms.incMsgCountById(message.rid, 1); return message; } // if message sent ONLY on a thread, skips the rest as it is done on a callback specific to threads if (message.tmid && !message.tshow) { - Rooms.incMsgCountById(message.rid, 1); + await Rooms.incMsgCountById(message.rid, 1); return message; } // Update all the room activity tracker fields - await RoomsRaw.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, settings.get('Store_Last_Message') && message); + await Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, settings.get('Store_Last_Message') && message); await updateUsersSubscriptions(message, room); diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.js b/apps/meteor/app/livechat/server/lib/RoutingManager.js index 13f1a878466..3b129377fc4 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.js +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; -import { LivechatInquiry, LivechatRooms, Subscriptions } from '@rocket.chat/models'; +import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms } from '@rocket.chat/models'; import { Message } from '@rocket.chat/core-services'; import { @@ -15,7 +15,7 @@ import { } from './Helper'; import { callbacks } from '../../../../lib/callbacks'; import { Logger } from '../../../../server/lib/logger/Logger'; -import { Rooms, Users } from '../../../models/server'; +import { Users } from '../../../models/server'; import { Apps, AppEvents } from '../../../../ee/server/apps'; const logger = new Logger('RoutingManager'); @@ -102,7 +102,7 @@ export const RoutingManager = { } await LivechatRooms.changeAgentByRoomId(rid, agent); - Rooms.incUsersCountById(rid); + await Rooms.incUsersCountById(rid); const user = Users.findOneById(agent.agentId); const room = await LivechatRooms.findOneById(rid); diff --git a/apps/meteor/app/models/server/models/Messages.js b/apps/meteor/app/models/server/models/Messages.js index e380df707c5..bee867f7a74 100644 --- a/apps/meteor/app/models/server/models/Messages.js +++ b/apps/meteor/app/models/server/models/Messages.js @@ -1,7 +1,7 @@ import _ from 'underscore'; +import { Rooms } from '@rocket.chat/models'; import { Base } from './_Base'; -import Rooms from './Rooms'; import { settings } from '../../../settings/server'; export class Messages extends Base { @@ -87,7 +87,7 @@ export class Messages extends Base { _.extend(record, extraData); record._id = this.insertOrUpsert(record); - Rooms.incMsgCountById(roomId, 1); + Promise.await(Rooms.incMsgCountById(roomId, 1)); return record; } diff --git a/apps/meteor/app/models/server/models/Rooms.js b/apps/meteor/app/models/server/models/Rooms.js index 1c575b71c6c..961e570f1a8 100644 --- a/apps/meteor/app/models/server/models/Rooms.js +++ b/apps/meteor/app/models/server/models/Rooms.js @@ -1,186 +1,17 @@ import { Base } from './_Base'; -import { trim } from '../../../../lib/utils/stringUtils'; class Rooms extends Base { - constructor(...args) { - super(...args); - - this.tryEnsureIndex({ name: 1 }, { unique: true, sparse: true }); - this.tryEnsureIndex({ default: 1 }, { sparse: true }); - this.tryEnsureIndex({ featured: 1 }, { sparse: true }); - this.tryEnsureIndex({ muted: 1 }, { sparse: true }); - this.tryEnsureIndex({ t: 1 }); - this.tryEnsureIndex({ 'u._id': 1 }); - this.tryEnsureIndex({ ts: 1 }); - // discussions - this.tryEnsureIndex({ prid: 1 }, { sparse: true }); - this.tryEnsureIndex({ fname: 1 }, { sparse: true }); - // field used for DMs only - this.tryEnsureIndex({ uids: 1 }, { sparse: true }); - this.tryEnsureIndex({ createdOTR: 1 }, { sparse: true }); - this.tryEnsureIndex({ encrypted: 1 }, { sparse: true }); // used on statistics - this.tryEnsureIndex({ broadcast: 1 }, { sparse: true }); // used on statistics - this.tryEnsureIndex({ 'streamingOptions.type': 1 }, { sparse: true }); // used on statistics - - this.tryEnsureIndex( - { - teamId: 1, - teamDefault: 1, - }, - { sparse: true }, - ); - } - findOneByImportId(_id, options) { const query = { importIds: _id }; return this.findOne(query, options); } - findOneByNonValidatedName(name, options) { - const room = this.findOneByNameOrFname(name, options); - if (room) { - return room; - } - - let channelName = trim(name); - try { - // TODO evaluate if this function call should be here - const { getValidRoomName } = Promise.await(import('../../../utils/server/lib/getValidRoomName')); - channelName = getValidRoomName(channelName, null, { allowDuplicates: true }); - } catch (e) { - console.error(e); - } - - return this.findOneByName(channelName, options); - } - findOneByName(name, options) { const query = { name }; return this.findOne(query, options); } - - findOneByNameOrFname(name, options) { - const query = { - $or: [ - { - name, - }, - { - fname: name, - }, - ], - }; - - return this.findOne(query, options); - } - - findOneByNameAndNotId(name, rid) { - const query = { - _id: { $ne: rid }, - name, - }; - - return this.findOne(query); - } - - findOneByDisplayName(fname, options) { - const query = { fname }; - - return this.findOne(query, options); - } - - // FIND - - findByDefaultAndTypes(defaultValue, types, options) { - const query = { - default: defaultValue, - t: { - $in: types, - }, - }; - - return this.find(query, options); - } - - findDirectRoomContainingAllUsernames(usernames, options) { - const query = { - t: 'd', - usernames: { $size: usernames.length, $all: usernames }, - usersCount: usernames.length, - }; - - return this.findOne(query, options); - } - - findOneDirectRoomContainingAllUserIDs(uid, options) { - const query = { - t: 'd', - uids: { $size: uid.length, $all: uid }, - }; - - return this.findOne(query, options); - } - - // UPDATE - - incMsgCountById(_id, inc = 1) { - const query = { _id }; - - const update = { - $inc: { - msgs: inc, - }, - }; - - return this.update(query, update); - } - - incUsersCountById(_id, inc = 1) { - const query = { _id }; - - const update = { - $inc: { - usersCount: inc, - }, - }; - - return this.update(query, update); - } - - incUsersCountByIds(ids, inc = 1) { - const query = { - _id: { - $in: ids, - }, - }; - - const update = { - $inc: { - usersCount: inc, - }, - }; - - return this.update(query, update, { multi: true }); - } - - incUsersCountNotDMsByIds(ids, inc = 1) { - const query = { - _id: { - $in: ids, - }, - t: { $ne: 'd' }, - }; - - const update = { - $inc: { - usersCount: inc, - }, - }; - - return this.update(query, update, { multi: true }); - } } export default new Rooms('room', true); diff --git a/apps/meteor/app/slackbridge/server/RocketAdapter.js b/apps/meteor/app/slackbridge/server/RocketAdapter.js index 98ea08c4c75..3416278ca63 100644 --- a/apps/meteor/app/slackbridge/server/RocketAdapter.js +++ b/apps/meteor/app/slackbridge/server/RocketAdapter.js @@ -239,8 +239,8 @@ export default class RocketAdapter { return `slack-${slackChannel}-${ts.replace(/\./g, '-')}`; } - findChannel(slackChannelId) { - return Rooms.findOneByImportId(slackChannelId); + async findChannel(slackChannelId) { + return RoomsRaw.findOneByImportId(slackChannelId); } getRocketUsers(members, slackChannel) { diff --git a/apps/meteor/app/slackbridge/server/SlackAdapter.js b/apps/meteor/app/slackbridge/server/SlackAdapter.js index 889ece9c48d..db97c73c769 100644 --- a/apps/meteor/app/slackbridge/server/SlackAdapter.js +++ b/apps/meteor/app/slackbridge/server/SlackAdapter.js @@ -4,7 +4,7 @@ import https from 'https'; import { RTMClient } from '@slack/rtm-api'; import { Meteor } from 'meteor/meteor'; -import { Messages as MessagesRaw } from '@rocket.chat/models'; +import { Messages as MessagesRaw, Rooms as RoomsRaw } from '@rocket.chat/models'; import { Message } from '@rocket.chat/core-services'; import { slackLogger } from './logger'; @@ -58,9 +58,9 @@ export default class SlackAdapter { this.registerForEvents(); - Meteor.startup(() => { + Meteor.startup(async () => { try { - this.populateMembershipChannelMap(); // If run outside of Meteor.startup, HTTP is not defined + await this.populateMembershipChannelMap(); // If run outside of Meteor.startup, HTTP is not defined } catch (err) { slackLogger.error({ msg: 'Error attempting to connect to Slack', err }); this.slackBridge.disconnect(); @@ -576,40 +576,42 @@ export default class SlackAdapter { return this.slackChannelRocketBotMembershipMap.get(rocketChID); } - populateMembershipChannelMapByChannels() { + async populateMembershipChannelMapByChannels() { const channels = this.slackAPI.getChannels(); if (!channels || channels.length <= 0) { return; } - for (const slackChannel of channels) { + for await (const slackChannel of channels) { const rocketchat_room = - Rooms.findOneByName(slackChannel.name, { fields: { _id: 1 } }) || Rooms.findOneByImportId(slackChannel.id, { fields: { _id: 1 } }); + Rooms.findOneByName(slackChannel.name, { fields: { _id: 1 } }) || + (await RoomsRaw.findOneByImportId(slackChannel.id, { projection: { _id: 1 } })); if (rocketchat_room && slackChannel.is_member) { this.addSlackChannel(rocketchat_room._id, slackChannel.id); } } } - populateMembershipChannelMapByGroups() { + async populateMembershipChannelMapByGroups() { const groups = this.slackAPI.getGroups(); if (!groups || groups.length <= 0) { return; } - for (const slackGroup of groups) { + for await (const slackGroup of groups) { const rocketchat_room = - Rooms.findOneByName(slackGroup.name, { fields: { _id: 1 } }) || Rooms.findOneByImportId(slackGroup.id, { fields: { _id: 1 } }); + Rooms.findOneByName(slackGroup.name, { fields: { _id: 1 } }) || + (await RoomsRaw.findOneByImportId(slackGroup.id, { projection: { _id: 1 } })); if (rocketchat_room && slackGroup.is_member) { this.addSlackChannel(rocketchat_room._id, slackGroup.id); } } } - populateMembershipChannelMap() { + async populateMembershipChannelMap() { slackLogger.debug('Populating channel map'); - this.populateMembershipChannelMapByChannels(); - this.populateMembershipChannelMapByGroups(); + await this.populateMembershipChannelMapByChannels(); + await this.populateMembershipChannelMapByGroups(); } /* diff --git a/apps/meteor/app/utils/server/lib/getValidRoomName.js b/apps/meteor/app/utils/server/lib/getValidRoomName.js index 04577e955cb..53df2cc7781 100644 --- a/apps/meteor/app/utils/server/lib/getValidRoomName.js +++ b/apps/meteor/app/utils/server/lib/getValidRoomName.js @@ -1,18 +1,18 @@ import { Meteor } from 'meteor/meteor'; import limax from 'limax'; import { escapeHTML } from '@rocket.chat/string-helpers'; +import { Rooms } from '@rocket.chat/models'; import { settings } from '../../../settings/server'; -import { Rooms } from '../../../models/server'; import { validateName } from '../../../lib/server/functions/validateName'; -export const getValidRoomName = (displayName, rid = '', options = {}) => { +export const getValidRoomName = async (displayName, rid = '', options = {}) => { let slugifiedName = displayName; if (settings.get('UI_Allow_room_names_with_special_chars')) { const cleanName = limax(displayName, { maintainCase: true }); if (options.allowDuplicates !== true) { - const room = Rooms.findOneByDisplayName(displayName); + const room = await Rooms.findOneByDisplayName(displayName); if (room && room._id !== rid) { if (room.archived) { throw new Meteor.Error('error-archived-duplicate-name', `There's an archived channel with name ${cleanName}`, { @@ -50,12 +50,13 @@ export const getValidRoomName = (displayName, rid = '', options = {}) => { } if (options.allowDuplicates !== true) { - const room = Rooms.findOneByName(slugifiedName); + const room = await Rooms.findOneByName(slugifiedName); if (room && room._id !== rid) { if (settings.get('UI_Allow_room_names_with_special_chars')) { let tmpName = slugifiedName; let next = 0; - while (Rooms.findOneByNameAndNotId(tmpName, rid)) { + // eslint-disable-next-line no-await-in-loop + while (await Rooms.findOneByNameAndNotId(tmpName, rid)) { tmpName = `${slugifiedName}-${++next}`; } slugifiedName = tmpName; diff --git a/apps/meteor/ee/server/lib/audit/methods.ts b/apps/meteor/ee/server/lib/audit/methods.ts index f994456ef8b..5c97754dc13 100644 --- a/apps/meteor/ee/server/lib/audit/methods.ts +++ b/apps/meteor/ee/server/lib/audit/methods.ts @@ -6,15 +6,15 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { ILivechatAgent, ILivechatVisitor, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import type { Mongo } from 'meteor/mongo'; -import { LivechatRooms } from '@rocket.chat/models'; +import { LivechatRooms, Rooms } from '@rocket.chat/models'; import AuditLog from './AuditLog'; -import { Rooms, Messages, Users } from '../../../../app/models/server'; +import { Messages, Users } from '../../../../app/models/server'; import { hasPermissionAsync } from '../../../../app/authorization/server/functions/hasPermission'; import { updateCounter } from '../../../../app/statistics/server'; import type { IAuditLog } from '../../../definition/IAuditLog'; -const getValue = (room?: IRoom) => room && { rids: [room._id], name: room.name }; +const getValue = (room: IRoom | null) => room && { rids: [room._id], name: room.name }; const getUsersIdFromUserName = (usernames: IUser['username'][]) => { const user: IUser[] = usernames ? Users.findByUsername({ $in: usernames }) : undefined; @@ -30,16 +30,16 @@ const getRoomInfoByAuditParams = async ({ }: { type: string; roomId: IRoom['_id']; - users: IUser['username'][]; + users: NonNullable[]; visitor: ILivechatVisitor['_id']; agent: ILivechatAgent['_id']; }) => { if (rid) { - return getValue(Rooms.findOne({ _id: rid })); + return getValue(await Rooms.findOne({ _id: rid })); } if (type === 'd') { - return getValue(Rooms.findDirectRoomContainingAllUsernames(usernames)); + return getValue(await Rooms.findDirectRoomContainingAllUsernames(usernames)); } if (type === 'l') { @@ -59,7 +59,7 @@ declare module '@rocket.chat/ui-contexts' { rid: IRoom['_id']; startDate: Date; endDate: Date; - users: IUser['username'][]; + users: NonNullable[]; msg: IMessage['msg']; type: string; visitor: ILivechatVisitor['_id']; @@ -68,7 +68,7 @@ declare module '@rocket.chat/ui-contexts' { auditGetOmnichannelMessages: (params: { startDate: Date; endDate: Date; - users: IUser['username'][]; + users: NonNullable[]; msg: IMessage['msg']; type: 'l'; visitor?: ILivechatVisitor['_id']; diff --git a/apps/meteor/ee/server/lib/ldap/Manager.ts b/apps/meteor/ee/server/lib/ldap/Manager.ts index 4d49d9e8f9b..8918271e637 100644 --- a/apps/meteor/ee/server/lib/ldap/Manager.ts +++ b/apps/meteor/ee/server/lib/ldap/Manager.ts @@ -1,11 +1,10 @@ import type ldapjs from 'ldapjs'; import type { ILDAPEntry, IUser, IRoom, IRole, IImportUser } from '@rocket.chat/core-typings'; -import { Users as UsersRaw, Roles, Subscriptions as SubscriptionsRaw } from '@rocket.chat/models'; +import { Users as UsersRaw, Roles, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models'; import { Team } from '@rocket.chat/core-services'; import type { ImporterAfterImportCallback } from '../../../../app/importer/server/definitions/IConversionCallbacks'; import { settings } from '../../../../app/settings/server'; -import { Rooms } from '../../../../app/models/server'; import { LDAPDataConverter } from '../../../../server/lib/ldap/DataConverter'; import { LDAPConnection } from '../../../../server/lib/ldap/Connection'; import { LDAPManager } from '../../../../server/lib/ldap/Manager'; @@ -14,6 +13,7 @@ import { addUserToRoom, removeUserFromRoom, createRoom } from '../../../../app/l import { syncUserRoles } from '../syncUserRoles'; import { ensureArray } from '../../../../lib/utils/arrayUtils'; import { copyCustomFieldsLDAP } from './copyCustomFieldsLDAP'; +import { getValidRoomName } from '../../../../app/utils/server'; export class LDAPEEManager extends LDAPManager { public static async sync(): Promise { @@ -302,7 +302,8 @@ export class LDAPEEManager extends LDAPManager { const channels: Array = [].concat(fieldMap[ldapField]); for await (const channel of channels) { try { - const room: IRoom | undefined = Rooms.findOneByNonValidatedName(channel) || (await this.createRoomForSync(channel)); + const name = await getValidRoomName(channel.trim(), undefined, { allowDuplicates: true }); + const room = (await Rooms.findOneByNonValidatedName(name)) || (await this.createRoomForSync(channel)); if (!room) { return; } diff --git a/apps/meteor/ee/server/lib/oauth/Manager.ts b/apps/meteor/ee/server/lib/oauth/Manager.ts index 24f8cdd2783..603490cbfe4 100644 --- a/apps/meteor/ee/server/lib/oauth/Manager.ts +++ b/apps/meteor/ee/server/lib/oauth/Manager.ts @@ -1,10 +1,10 @@ -import { Roles } from '@rocket.chat/models'; +import { Roles, Rooms } from '@rocket.chat/models'; import type { IUser } from '@rocket.chat/core-typings'; -import { Rooms } from '../../../../app/models/server'; import { addUserToRoom, createRoom } from '../../../../app/lib/server/functions'; import { Logger } from '../../../../app/logger/server'; import { syncUserRoles } from '../syncUserRoles'; +import { getValidRoomName } from '../../../../app/utils/server'; const logger = new Logger('OAuth'); @@ -26,15 +26,19 @@ export class OAuthEEManager { channels = [channels]; } for await (const channel of channels) { - let room = Rooms.findOneByNonValidatedName(channel); + const name = await getValidRoomName(channel.trim(), undefined, { allowDuplicates: true }); + let room = await Rooms.findOneByNonValidatedName(name); if (!room) { - room = await createRoom('c', channel, channelsAdmin, [], false); - if (!room?.rid) { + const createdRoom = await createRoom('c', channel, channelsAdmin, [], false); + if (!createdRoom?.rid) { logger.error(`could not create channel ${channel}`); return; } + + room = createdRoom; } - if (Array.isArray(groupsFromSSO) && groupsFromSSO.includes(ssoGroup)) { + + if (room && Array.isArray(groupsFromSSO) && groupsFromSSO.includes(ssoGroup)) { await addUserToRoom(room._id, user); } } diff --git a/apps/meteor/server/methods/createDirectMessage.ts b/apps/meteor/server/methods/createDirectMessage.ts index ccc34cfd82e..be279a2dfe7 100644 --- a/apps/meteor/server/methods/createDirectMessage.ts +++ b/apps/meteor/server/methods/createDirectMessage.ts @@ -3,10 +3,11 @@ import { check, Match } from 'meteor/check'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import type { ICreatedRoom, IUser } from '@rocket.chat/core-typings'; import type { ICreateRoomParams } from '@rocket.chat/core-services'; +import { Rooms } from '@rocket.chat/models'; import { settings } from '../../app/settings/server'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; -import { Users, Rooms } from '../../app/models/server'; +import { Users } from '../../app/models/server'; import { RateLimiterClass as RateLimiter } from '../../app/lib/server/lib/RateLimiter'; import { createRoom } from '../../app/lib/server/functions/createRoom'; import { addUser } from '../../app/federation/server/functions/addUser'; @@ -80,12 +81,12 @@ export async function createDirectMessage( if (await hasPermissionAsync(userId, 'view-d-room')) { // Check if the direct room already exists, then return it const uids = roomUsers.map(({ _id }) => _id).sort(); - const room = Rooms.findOneDirectRoomContainingAllUserIDs(uids, { fields: { _id: 1 } }); + const room = await Rooms.findOneDirectRoomContainingAllUserIDs(uids, { projection: { _id: 1 } }); if (room) { return { + ...room, t: 'd', rid: room._id, - ...room, }; } } diff --git a/apps/meteor/server/models/raw/Rooms.js b/apps/meteor/server/models/raw/Rooms.js index 9c796f2beb8..f264ad78441 100644 --- a/apps/meteor/server/models/raw/Rooms.js +++ b/apps/meteor/server/models/raw/Rooms.js @@ -493,6 +493,30 @@ export class RoomsRaw extends BaseRaw { return this.col.aggregate(params, { allowDiskUse: true, readPreference }); } + findOneByNameOrFname(name, options) { + const query = { + $or: [ + { + name, + }, + { + fname: name, + }, + ], + }; + + return this.findOne(query, options); + } + + async findOneByNonValidatedName(name, options) { + const room = await this.findOneByNameOrFname(name, options); + if (room) { + return room; + } + + return this.findOneByName(name, options); + } + findOneByName(name, options = {}) { return this.col.findOne({ name }, options); } @@ -523,10 +547,6 @@ export class RoomsRaw extends BaseRaw { return this.updateMany(query, update); } - findOneByNameOrFname(name, options = {}) { - return this.col.findOne({ $or: [{ name }, { fname: name }] }, options); - } - allRoomSourcesCount() { return this.col.aggregate([ { diff --git a/apps/meteor/server/publications/room/index.ts b/apps/meteor/server/publications/room/index.ts index 31c3d1f41e4..56b4c986e8a 100644 --- a/apps/meteor/server/publications/room/index.ts +++ b/apps/meteor/server/publications/room/index.ts @@ -33,7 +33,7 @@ Meteor.methods({ if (!user) { if (settings.get('Accounts_AllowAnonymousRead')) { - return Rooms.findByDefaultAndTypes(true, ['c'], options).fetch(); + return RoomsRaw.findByDefaultAndTypes(true, ['c'], options).toArray(); } return []; } diff --git a/packages/model-typings/src/models/IRoomsModel.ts b/packages/model-typings/src/models/IRoomsModel.ts index 09c0bbc4e9b..68faa0897d0 100644 --- a/packages/model-typings/src/models/IRoomsModel.ts +++ b/packages/model-typings/src/models/IRoomsModel.ts @@ -67,6 +67,7 @@ export interface IRoomsModel extends IBaseModel { incUsersCountByIds(ids: any, inc: number): any; findOneByNameOrFname(name: any, options?: any): any; + findOneByNonValidatedName(name: string, options?: FindOptions): Promise; allRoomSourcesCount(): AggregationCursor; // TODO change back when convert model do TS AggregationCursor<{ _id: Required; count: number }>; @@ -167,7 +168,7 @@ export interface IRoomsModel extends IBaseModel { includeFederatedRooms?: boolean, ): FindCursor; findByDefaultAndTypes(defaultValue: boolean, types: IRoom['t'][], options?: FindOptions): FindCursor; - findDirectRoomContainingAllUsernames(usernames: string[], options?: FindOptions): FindCursor; + findDirectRoomContainingAllUsernames(usernames: string[], options?: FindOptions): Promise; findByTypeAndName(type: IRoom['t'], name: string, options?: FindOptions): Promise; findByTypeAndNameOrId(type: IRoom['t'], name: string, options?: FindOptions): Promise; findByTypeAndNameContaining(type: IRoom['t'], name: string, options?: FindOptions): FindCursor;