refactor: move subscriptions 4x (#28548)

pull/28670/head
Kevin Aleman 3 years ago committed by GitHub
parent 366b8e9799
commit 9ea35c8e45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      apps/meteor/app/api/server/lib/rooms.ts
  2. 28
      apps/meteor/app/api/server/v1/channels.ts
  3. 12
      apps/meteor/app/api/server/v1/groups.ts
  4. 8
      apps/meteor/app/api/server/v1/users.ts
  5. 8
      apps/meteor/app/apps/server/bridges/rooms.ts
  6. 6
      apps/meteor/app/irc/server/irc-bridge/localHandlers/onSaveMessage.js
  7. 16
      apps/meteor/app/lib/server/functions/deleteUser.ts
  8. 30
      apps/meteor/app/lib/server/functions/getRoomsWithSingleOwner.ts
  9. 2
      apps/meteor/app/lib/server/functions/setUserActiveStatus.ts
  10. 16
      apps/meteor/app/lib/server/functions/updateGroupDMsName.ts
  11. 4
      apps/meteor/app/livechat/server/lib/Helper.js
  12. 10
      apps/meteor/app/livechat/server/lib/RoutingManager.js
  13. 12
      apps/meteor/app/mentions/server/Mentions.js
  14. 5
      apps/meteor/app/mentions/server/server.js
  15. 120
      apps/meteor/app/models/server/models/Subscriptions.js
  16. 7
      apps/meteor/app/models/server/models/Users.js
  17. 9
      apps/meteor/server/lib/dataExport/processDataDownloads.ts
  18. 10
      apps/meteor/server/lib/roles/getRoomRoles.ts
  19. 17
      apps/meteor/server/lib/spotlight.js
  20. 7
      apps/meteor/server/methods/browseChannels.ts
  21. 14
      apps/meteor/server/methods/channelsList.ts
  22. 9
      apps/meteor/server/methods/messageSearch.ts
  23. 2
      apps/meteor/server/models/raw/Rooms.js
  24. 18
      apps/meteor/server/models/raw/Subscriptions.ts
  25. 14
      apps/meteor/server/publications/subscription/index.ts
  26. 2
      apps/meteor/server/services/team/service.ts
  27. 2
      packages/model-typings/src/models/ISubscriptionsModel.ts

@ -1,8 +1,7 @@
import type { IRoom, ISubscription, RoomAdminFieldsType } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';
import { Rooms, Subscriptions } from '@rocket.chat/models';
import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { Subscriptions } from '../../../models/server';
import { adminFields } from '../../../../lib/rooms/adminFields';
export async function findAdminRooms({
@ -82,9 +81,9 @@ export async function findChannelAndPrivateAutocomplete({ uid, selector }: { uid
},
};
const userRoomsIds = Subscriptions.cachedFindByUserId(uid, { fields: { rid: 1 } })
.fetch()
.map((item: Pick<ISubscription, 'rid'>) => item.rid);
const userRoomsIds = (await Subscriptions.findByUserId(uid, { projection: { rid: 1 } }).toArray()).map(
(item: Pick<ISubscription, 'rid'>) => item.rid,
);
const rooms = await Rooms.findRoomsWithoutDiscussionsByRoomIds(selector.name, userRoomsIds, options).toArray();
@ -132,9 +131,9 @@ export async function findChannelAndPrivateAutocompleteWithPagination({
items: IRoom[];
total: number;
}> {
const userRoomsIds = Subscriptions.cachedFindByUserId(uid, { fields: { rid: 1 } })
.fetch()
.map((item: Pick<ISubscription, 'rid'>) => item.rid);
const userRoomsIds = (await Subscriptions.findByUserId(uid, { projection: { rid: 1 } }).toArray()).map(
(item: Pick<ISubscription, 'rid'>) => item.rid,
);
const options = {
projection: {
@ -177,7 +176,7 @@ export async function findRoomsAvailableForTeams({ uid, name }: { uid: string; n
};
const userRooms = (
Subscriptions.findByUserIdAndRoles(uid, ['owner'], { fields: { rid: 1 } }).fetch() as Pick<ISubscription, 'rid'>[]
(await Subscriptions.findByUserIdAndRoles(uid, ['owner'], { projection: { rid: 1 } }).toArray()) as Pick<ISubscription, 'rid'>[]
).map((item) => item.rid);
const rooms = await Rooms.findChannelAndGroupListWithoutTeamsByNameStartingByOwner(uid, name, userRooms, options).toArray();

@ -21,7 +21,7 @@ import {
import { Integrations, Messages, Rooms, Subscriptions, Uploads } from '@rocket.chat/models';
import { Team } from '@rocket.chat/core-services';
import { Subscriptions as SubscriptionsSync, Users as UsersSync } from '../../../models/server';
import { Users as UsersSync } from '../../../models/server';
import { canAccessRoomAsync, hasAtLeastOnePermission } from '../../../authorization/server';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
@ -425,11 +425,11 @@ API.v1.addRoute(
const findResult = await findChannelByIdOrName({ params });
const moderators = await SubscriptionsSync.findByRoomIdAndRoles(findResult._id, ['moderator'], {
fields: { u: 1 },
})
.fetch()
.map((sub: ISubscription) => sub.u);
const moderators = (
await Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], {
projection: { u: 1 },
}).toArray()
).map((sub: ISubscription) => sub.u);
return API.v1.success({
moderators,
@ -896,18 +896,18 @@ API.v1.addRoute(
if (!(await hasPermissionAsync(this.userId, 'view-joined-room'))) {
return API.v1.unauthorized();
}
const roomIds = await SubscriptionsSync.findByUserIdAndType(this.userId, 'c', {
fields: { rid: 1 },
})
.fetch()
.map((s: Record<string, any>) => s.rid);
const roomIds = (
await Subscriptions.findByUserIdAndType(this.userId, 'c', {
projection: { rid: 1 },
}).toArray()
).map((s) => s.rid);
ourQuery._id = { $in: roomIds };
}
// teams filter - I would love to have a way to apply this filter @ db level :(
const ids = await SubscriptionsSync.cachedFindByUserId(this.userId, { fields: { rid: 1 } })
.fetch()
.map((item: Record<string, any>) => item.rid);
const ids = (await Subscriptions.findByUserId(this.userId, { projection: { rid: 1 } }).toArray()).map(
(item: Record<string, any>) => item.rid,
);
ourQuery.$or = [
{

@ -5,7 +5,7 @@ import { Subscriptions, Rooms, Messages, Users, Uploads, Integrations } from '@r
import { Team } from '@rocket.chat/core-services';
import type { Filter } from 'mongodb';
import { Users as UsersSync, Subscriptions as SubscriptionsSync } from '../../../models/server';
import { Users as UsersSync } from '../../../models/server';
import { hasAtLeastOnePermission, canAccessRoomAsync, hasAllPermission, roomAccessAttributes } from '../../../authorization/server';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { API } from '../api';
@ -1150,11 +1150,11 @@ API.v1.addRoute(
userId: this.userId,
});
const moderators = await SubscriptionsSync.findByRoomIdAndRoles(findResult.rid, ['moderator'], {
fields: { u: 1 },
})
.fetch()
.map((sub: any) => sub.u);
const moderators = (
await Subscriptions.findByRoomIdAndRoles(findResult.rid, ['moderator'], {
projection: { u: 1 },
}).toArray()
).map((sub: any) => sub.u);
return API.v1.success({
moderators,

@ -19,11 +19,11 @@ import { Accounts } from 'meteor/accounts-base';
import { Match, check } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import type { IExportOperation, ILoginToken, IPersonalAccessToken, IUser } from '@rocket.chat/core-typings';
import { Users as UsersRaw } from '@rocket.chat/models';
import { Users as UsersRaw, Subscriptions } from '@rocket.chat/models';
import type { Filter } from 'mongodb';
import { Team, api } from '@rocket.chat/core-services';
import { Users, Subscriptions } from '../../../models/server';
import { Users } from '../../../models/server';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { settings } from '../../../settings/server';
import { validateCustomFields, saveUser, saveCustomFieldsWithoutValidation, setUserAvatar, saveCustomFields } from '../../../lib/server';
@ -376,7 +376,7 @@ API.v1.addRoute(
return API.v1.success({
user: {
...user,
rooms: Subscriptions.findByUserId(user._id, {
rooms: await Subscriptions.findByUserId(user._id, {
projection: {
rid: 1,
name: 1,
@ -389,7 +389,7 @@ API.v1.addRoute(
t: 1,
name: 1,
},
}).fetch(),
}).toArray(),
},
});
}

@ -5,9 +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 type { AppServerOrchestrator } from '../../../../ee/server/apps/orchestrator';
import { Rooms, Subscriptions, Users } from '../../../models/server';
import { Rooms, Users } from '../../../models/server';
import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom';
import { deleteRoom } from '../../../lib/server/functions/deleteRoom';
@ -95,7 +96,7 @@ export class AppRoomBridge extends RoomBridge {
protected async getMembers(roomId: string, appId: string): Promise<Array<IUser>> {
this.orch.debugLog(`The App ${appId} is getting the room's members by room id: "${roomId}"`);
const subscriptions = await Subscriptions.findByRoomId(roomId, {});
return subscriptions.map((sub: ISubscription) => this.orch.getConverters()?.get('users').convertById(sub.u?._id));
return (await subscriptions.toArray()).map((sub: ISubscription) => this.orch.getConverters()?.get('users').convertById(sub.u?._id));
}
protected async getDirectByUsernames(usernames: Array<string>, appId: string): Promise<IRoom | undefined> {
@ -188,7 +189,8 @@ export class AppRoomBridge extends RoomBridge {
private async getUsersByRoomIdAndSubscriptionRole(roomId: string, role: string): Promise<IUser[]> {
const subs = await Subscriptions.findByRoomIdAndRoles(roomId, [role], { projection: { uid: '$u._id', _id: 0 } });
const users = await Users.findByIds(subs.map((user: { uid: string }) => user.uid));
// Was this a bug?
const users = await Users.findByIds(subs.map((user) => user.u._id));
const userConverter = this.orch.getConverters()!.get('users');
return users.map((user: ICoreUser) => userConverter!.convertToApp(user));
}

@ -1,12 +1,14 @@
import { Subscriptions } from '@rocket.chat/models';
import { SystemLogger } from '../../../../../server/lib/logger/system';
import { Subscriptions, Users } from '../../../../models/server';
import { Users } from '../../../../models/server';
export default async function handleOnSaveMessage(message, to) {
let toIdentification = '';
// Direct message
if (to.t === 'd') {
const subscriptions = Subscriptions.findByRoomId(to._id);
subscriptions.forEach((subscription) => {
await subscriptions.forEach((subscription) => {
if (subscription.u._id !== message.u._id) {
const userData = Users.findOne({ username: subscription.u.username });
if (userData) {

@ -1,10 +1,18 @@
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { Integrations, FederationServers, LivechatVisitors, LivechatDepartmentAgents, Messages, Rooms } from '@rocket.chat/models';
import {
Integrations,
FederationServers,
LivechatVisitors,
LivechatDepartmentAgents,
Messages,
Rooms,
Subscriptions,
} from '@rocket.chat/models';
import { api } from '@rocket.chat/core-services';
import { FileUpload } from '../../../file-upload/server';
import { Users, Subscriptions } from '../../../models/server';
import { Users } from '../../../models/server';
import { settings } from '../../../settings/server';
import { updateGroupDMsName } from './updateGroupDMsName';
import { relinquishRoomOwnerships } from './relinquishRoomOwnerships';
@ -21,7 +29,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false): Pro
return;
}
const subscribedRooms = getSubscribedRoomsForUserWithDetails(userId);
const subscribedRooms = await getSubscribedRoomsForUserWithDetails(userId);
if (shouldRemoveOrChangeOwner(subscribedRooms) && !confirmRelinquish) {
const rooms = await getUserSingleOwnedRooms(subscribedRooms);
@ -57,7 +65,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false): Pro
await Rooms.updateGroupDMsRemovingUsernamesByUsername(user.username, userId); // Remove direct rooms with the user
await Rooms.removeDirectRoomContainingUsername(user.username); // Remove direct rooms with the user
Subscriptions.removeByUserId(userId); // Remove user subscriptions
await Subscriptions.removeByUserId(userId); // Remove user subscriptions
if (user.roles.includes('livechat-agent')) {
// Remove user as livechat agent

@ -1,7 +1,7 @@
import type { IUser, ISubscription } from '@rocket.chat/core-typings';
import type { IUser } from '@rocket.chat/core-typings';
import { Subscriptions, Users } from '@rocket.chat/models';
import { subscriptionHasRole } from '../../../authorization/server';
import { Users, Subscriptions } from '../../../models/server';
export type SubscribedRoomsForUserWithDetails = {
rid: string;
@ -16,18 +16,18 @@ export function shouldRemoveOrChangeOwner(subscribedRooms: SubscribedRoomsForUse
return subscribedRooms.some(({ shouldBeRemoved, shouldChangeOwner }) => shouldBeRemoved || shouldChangeOwner);
}
export function getSubscribedRoomsForUserWithDetails(
export async function getSubscribedRoomsForUserWithDetails(
userId: string,
assignNewOwner = true,
roomIds: string[] = [],
): SubscribedRoomsForUserWithDetails[] {
): Promise<SubscribedRoomsForUserWithDetails[]> {
const subscribedRooms: SubscribedRoomsForUserWithDetails[] = [];
const cursor =
roomIds.length > 0 ? Subscriptions.findByUserIdAndRoomIds(userId, roomIds) : Subscriptions.findByUserIdExceptType(userId, 'd');
// Iterate through all the rooms the user is subscribed to, to check if he is the last owner of any of them.
cursor.forEach((subscription: ISubscription) => {
for await (const subscription of cursor) {
const roomData: SubscribedRoomsForUserWithDetails = {
rid: subscription.rid,
t: subscription.t,
@ -39,27 +39,29 @@ export function getSubscribedRoomsForUserWithDetails(
if (subscriptionHasRole(subscription, 'owner')) {
// Fetch the number of owners
const numOwners = Subscriptions.findByRoomIdAndRoles(subscription.rid, ['owner']).count();
const numOwners = await Subscriptions.countByRoomIdAndRoles(subscription.rid, ['owner']);
// If it's only one, then this user is the only owner.
roomData.userIsLastOwner = numOwners === 1;
if (numOwners === 1 && assignNewOwner) {
// Let's check how many subscribers the room has.
const options = { projection: { 'u._id': 1 }, sort: { ts: 1 } };
const options = { projection: { 'u._id': 1 }, sort: { ts: 1 as const } };
const subscribersCursor = Subscriptions.findByRoomId(subscription.rid, options);
subscribersCursor.forEach(({ u: { _id: uid } }: ISubscription) => {
for await (const {
u: { _id: uid },
} of subscribersCursor) {
// If we already changed the owner or this subscription is for the user we are removing, then don't try to give it ownership
if (roomData.shouldChangeOwner || uid === userId) {
return;
continue;
}
const newOwner = Users.findOneActiveById(uid, { fields: { _id: 1 } });
const newOwner = await Users.findOneActiveById(uid, { projection: { _id: 1 } });
if (!newOwner) {
return;
continue;
}
roomData.newOwner = uid;
roomData.shouldChangeOwner = true;
});
}
// If there's no subscriber available to be the new owner and it's not a public room, we can remove it.
if (!roomData.shouldChangeOwner && roomData.t !== 'c') {
@ -68,11 +70,11 @@ export function getSubscribedRoomsForUserWithDetails(
}
} else if (roomData.t !== 'c') {
// If the user is not an owner, remove the room if the user is the only subscriber
roomData.shouldBeRemoved = Subscriptions.findByRoomId(roomData.rid).count() === 1;
roomData.shouldBeRemoved = (await Subscriptions.countByRoomId(roomData.rid)) === 1;
}
subscribedRooms.push(roomData);
});
}
return subscribedRooms;
}

@ -68,7 +68,7 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi
});
}
const subscribedRooms = getSubscribedRoomsForUserWithDetails(userId);
const subscribedRooms = await getSubscribedRoomsForUserWithDetails(userId);
// give omnichannel rooms a special treatment :)
const chatSubscribedRooms = subscribedRooms.filter(({ t }) => t !== 'l');
const livechatSubscribedRooms = subscribedRooms.filter(({ t }) => t === 'l');

@ -1,7 +1,7 @@
import type { IUser, ISubscription } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';
import type { IUser } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions } from '@rocket.chat/models';
import { Subscriptions, Users } from '../../../models/server';
import { Users } from '../../../models/server';
const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', ');
const getName = (members: IUser[]): string => members.map(({ username }) => username).join(',');
@ -34,7 +34,7 @@ function sortUsersAlphabetically(u1: IUser, u2: IUser): number {
return (u1.name! || u1.username!).localeCompare(u2.name! || u2.username!);
}
export const updateGroupDMsName = async (userThatChangedName: IUser) => {
export const updateGroupDMsName = async (userThatChangedName: IUser): Promise<void> => {
if (!userThatChangedName.username) {
return;
}
@ -60,10 +60,10 @@ export const updateGroupDMsName = async (userThatChangedName: IUser) => {
const members = getMembers(room.uids);
const sortedMembers = members.sort(sortUsersAlphabetically);
const subs = Subscriptions.findByRoomId(room._id, { fields: { '_id': 1, 'u._id': 1 } });
subs.forEach((sub: ISubscription) => {
const subs = Subscriptions.findByRoomId(room._id, { projection: { '_id': 1, 'u._id': 1 } });
for await (const sub of subs) {
const otherMembers = sortedMembers.filter(({ _id }) => _id !== sub.u._id);
Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers));
});
await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers));
}
}
};

@ -375,7 +375,7 @@ export const forwardRoomToAgent = async (room, transferData) => {
const { servedBy } = roomTaken;
if (servedBy) {
if (oldServedBy && servedBy._id !== oldServedBy._id) {
RoutingManager.removeAllRoomSubscriptions(room, servedBy);
await RoutingManager.removeAllRoomSubscriptions(room, servedBy);
}
Messages.createUserJoinWithRoomIdAndUser(rid, {
_id: servedBy._id,
@ -489,7 +489,7 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => {
if (oldServedBy) {
// if chat is queued then we don't ignore the new servedBy agent bcs at this
// point the chat is not assigned to him/her and it is still in the queue
RoutingManager.removeAllRoomSubscriptions(room, !chatQueued && servedBy);
await RoutingManager.removeAllRoomSubscriptions(room, !chatQueued && servedBy);
}
if (!chatQueued && servedBy) {
Messages.createUserJoinWithRoomIdAndUser(rid, servedBy);

@ -1,6 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { LivechatInquiry, LivechatRooms } from '@rocket.chat/models';
import { LivechatInquiry, LivechatRooms, Subscriptions } from '@rocket.chat/models';
import {
createLivechatSubscription,
@ -14,7 +14,7 @@ import {
} from './Helper';
import { callbacks } from '../../../../lib/callbacks';
import { Logger } from '../../../../server/lib/logger/Logger';
import { Rooms, Messages, Users, Subscriptions } from '../../../models/server';
import { Rooms, Messages, Users } from '../../../models/server';
import { Apps, AppEvents } from '../../../../ee/server/apps';
const logger = new Logger('RoutingManager');
@ -140,7 +140,7 @@ export const RoutingManager = {
if (servedBy) {
logger.debug(`Unassigning current agent for inquiry ${inquiry._id}`);
await LivechatRooms.removeAgentByRoomId(rid);
this.removeAllRoomSubscriptions(room);
await this.removeAllRoomSubscriptions(room);
dispatchAgentDelegated(rid, null);
}
@ -239,10 +239,10 @@ export const RoutingManager = {
return defaultAgent;
},
removeAllRoomSubscriptions(room, ignoreUser) {
async removeAllRoomSubscriptions(room, ignoreUser) {
const { _id: roomId } = room;
const subscriptions = Subscriptions.findByRoomId(roomId).fetch();
const subscriptions = await Subscriptions.findByRoomId(roomId).toArray();
subscriptions?.forEach(({ u }) => {
if (ignoreUser && ignoreUser._id === u._id) {
return;

@ -53,19 +53,21 @@ export default class MentionsServer extends MentionsParser {
const mentionsAll = [];
const userMentions = [];
mentions.forEach((m) => {
for await (const m of mentions) {
const mention = m.trim().substr(1);
if (mention !== 'all' && mention !== 'here') {
return userMentions.push(mention);
userMentions.push(mention);
continue;
}
if (this.messageMaxAll > 0 && this.getTotalChannelMembers(rid) > this.messageMaxAll) {
return this.onMaxRoomMembersExceeded({ sender, rid });
if (this.messageMaxAll > 0 && (await this.getTotalChannelMembers(rid)) > this.messageMaxAll) {
this.onMaxRoomMembersExceeded({ sender, rid });
continue;
}
mentionsAll.push({
_id: mention,
username: mention,
});
});
}
mentions = userMentions.length ? await this.getUsers(userMentions) : [];
return [...mentionsAll, ...mentions];
}

@ -1,11 +1,12 @@
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { api } from '@rocket.chat/core-services';
import { Subscriptions } from '@rocket.chat/models';
import MentionsServer from './Mentions';
import { settings } from '../../settings/server';
import { callbacks } from '../../../lib/callbacks';
import { Users, Subscriptions, Rooms } from '../../models/server';
import { Users, Rooms } from '../../models/server';
export class MentionQueries {
async getUsers(usernames) {
@ -24,7 +25,7 @@ export class MentionQueries {
}
getTotalChannelMembers(rid) {
return Subscriptions.findByRoomId(rid).count();
return Subscriptions.countByRoomId(rid);
}
getChannels(channels) {

@ -1,6 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import mem from 'mem';
import { Base } from './_Base';
import Rooms from './Rooms';
@ -68,126 +67,7 @@ class Subscriptions extends Base {
return this.findOne(query, options);
}
findOneByRoomNameAndUserId(roomName, userId) {
const query = {
'name': roomName,
'u._id': userId,
};
return this.findOne(query);
}
// FIND
findByUserId(userId, options) {
const query = { 'u._id': userId };
return this.find(query, options);
}
cachedFindByUserId = mem(this.findByUserId.bind(this), { maxAge: 5000 });
findByUserIdExceptType(userId, typeException, options) {
const query = {
'u._id': userId,
't': { $ne: typeException },
};
return this.find(query, options);
}
findByUserIdAndRoomIds(userId, roomIds, options) {
const query = {
'u._id': userId,
'rid': { $in: roomIds },
};
return this.find(query, options);
}
findByUserIdAndType(userId, type, options) {
const query = {
'u._id': userId,
't': type,
};
return this.find(query, options);
}
findByUserIdAndTypes(userId, types, options) {
const query = {
'u._id': userId,
't': {
$in: types,
},
};
return this.find(query, options);
}
/**
* @param {IUser['_id']} userId
* @param {IRole['_id'][]} roles
* @param {any} options
*/
findByUserIdAndRoles(userId, roles, options) {
const query = {
'u._id': userId,
'roles': { $in: roles },
};
return this.find(query, options);
}
findByUserIdUpdatedAfter(userId, updatedAt, options) {
const query = {
'u._id': userId,
'_updatedAt': {
$gt: updatedAt,
},
};
return this.find(query, options);
}
/**
* @param {string} roomId
* @param {IRole['_id'][]} roles the list of roles
* @param {any} options
*/
findByRoomIdAndRoles(roomId, roles, options = undefined) {
roles = [].concat(roles);
const query = {
rid: roomId,
roles: { $in: roles },
};
return this.find(query, options);
}
findByType(types, options) {
const query = {
t: {
$in: types,
},
};
return this.find(query, options);
}
findByTypeAndUserId(type, userId, options) {
const query = {
't': type,
'u._id': userId,
};
return this.find(query, options);
}
findByRoomId(roomId, options) {
const query = { rid: roomId };
return this.find(query, options);
}
findByRoomIdAndNotUserId(roomId, userId, options = {}) {
const query = {
'rid': roomId,

@ -1,9 +1,9 @@
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Subscriptions } from '@rocket.chat/models';
import { Base } from './_Base';
import Subscriptions from './Subscriptions';
import { settings } from '../../../settings/server';
import { trim } from '../../../../lib/utils/stringUtils';
@ -31,6 +31,7 @@ const queryStatusAgentOnline = (extraFilters = {}) => ({
statusConnection: { $ne: 'away' },
}),
});
// The promise.await will die with the model :)
export class Users extends Base {
constructor(...args) {
super(...args);
@ -693,9 +694,7 @@ export class Users extends Base {
}
findByRoomId(rid, options) {
const data = Subscriptions.findByRoomId(rid)
.fetch()
.map((item) => item.u._id);
const data = Promise.await(Subscriptions.findByRoomId(rid).toArray()).map((item) => item.u._id);
const query = {
_id: {
$in: data,

@ -4,11 +4,10 @@ import { access, mkdir, rm, writeFile } from 'fs/promises';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { Avatars, ExportOperations, UserDataFiles } from '@rocket.chat/models';
import { Avatars, ExportOperations, UserDataFiles, Subscriptions } from '@rocket.chat/models';
import type { IExportOperation, ISubscription, IUser, RoomType } from '@rocket.chat/core-typings';
import { settings } from '../../../app/settings/server';
import { Subscriptions } from '../../../app/models/server';
import { FileUpload } from '../../../app/file-upload/server';
import { getPath } from './getPath';
import { joinPath } from '../fileUtils';
@ -20,7 +19,7 @@ import { copyFileUpload } from './copyFileUpload';
import { uploadZipFile } from './uploadZipFile';
import { exportRoomMessagesToFile } from './exportRoomMessagesToFile';
const loadUserSubscriptions = (_exportOperation: IExportOperation, fileType: 'json' | 'html', userId: IUser['_id']) => {
const loadUserSubscriptions = async (_exportOperation: IExportOperation, fileType: 'json' | 'html', userId: IUser['_id']) => {
const roomList: (
| {
roomId: string;
@ -35,7 +34,7 @@ const loadUserSubscriptions = (_exportOperation: IExportOperation, fileType: 'js
)[] = [];
const cursor = Subscriptions.findByUserId(userId);
cursor.forEach((subscription: ISubscription) => {
await cursor.forEach((subscription: ISubscription) => {
const roomData = getRoomData(subscription.rid, userId);
roomData.targetFile = `${(fileType === 'json' && roomData.roomName) || subscription.rid}.${fileType}`;
@ -147,7 +146,7 @@ const continueExportOperation = async function (exportOperation: IExportOperatio
const exportType = exportOperation.fullExport ? 'json' : 'html';
if (!exportOperation.roomList) {
exportOperation.roomList = loadUserSubscriptions(exportOperation, exportType, exportOperation.userId);
exportOperation.roomList = await loadUserSubscriptions(exportOperation, exportType, exportOperation.userId);
if (exportOperation.fullExport) {
exportOperation.status = 'exporting-rooms';

@ -1,16 +1,16 @@
import _ from 'underscore';
import type { IRoom, ISubscription } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';
import { Roles, Subscriptions } from '@rocket.chat/models';
import { settings } from '../../../app/settings/server';
import { Subscriptions, Users } from '../../../app/models/server';
import { Users } from '../../../app/models/server';
export async function getRoomRoles(rid: IRoom['_id']): Promise<ISubscription[]> {
const options = {
sort: {
'u.username': 1,
'u.username': 1 as const,
},
fields: {
projection: {
rid: 1,
u: 1,
roles: 1,
@ -20,7 +20,7 @@ export async function getRoomRoles(rid: IRoom['_id']): Promise<ISubscription[]>
const useRealName = settings.get('UI_Use_Real_Name') === true;
const roles = await Roles.find({ scope: 'Subscriptions', description: { $exists: true, $ne: '' } }).toArray();
const subscriptions = Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).fetch() as ISubscription[];
const subscriptions = await Subscriptions.findByRoomIdAndRoles(rid, _.pluck(roles, '_id'), options).toArray();
if (!useRealName) {
return subscriptions;

@ -3,7 +3,7 @@ import { Users, Subscriptions as SubscriptionsRaw, Rooms as RoomsRaw } from '@ro
import { hasAllPermission, canAccessRoomAsync, roomAccessAttributes } from '../../app/authorization/server';
import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
import { Subscriptions, Rooms } from '../../app/models/server';
import { Rooms } from '../../app/models/server';
import { settings } from '../../app/settings/server';
import { readSecondaryPreferred } from '../database/readSecondaryPreferred';
import { roomCoordinator } from './rooms/roomCoordinator';
@ -53,11 +53,11 @@ export class Spotlight {
const searchableRoomTypeIds = roomCoordinator.searchableRoomTypes();
const roomIds = Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypeIds, {
fields: { rid: 1 },
})
.fetch()
.map((s) => s.rid);
const roomIds = (
await SubscriptionsRaw.findByUserIdAndTypes(userId, searchableRoomTypeIds, {
projection: { rid: 1 },
}).toArray()
).map((s) => s.rid);
const exactRoom = await RoomsRaw.findOneByNameAndType(text, searchableRoomTypeIds, roomOptions, includeFederatedRooms);
if (exactRoom) {
roomIds.push(exactRoom.rid);
@ -177,10 +177,7 @@ export class Spotlight {
case 'l':
insiderExtraQuery.push({
_id: {
$in: Subscriptions.findByRoomId(room._id)
.fetch()
.map((s) => s.u?._id)
.filter((id) => id && id !== userId),
$in: (await SubscriptionsRaw.findByRoomId(room._id).toArray()).map((s) => s.u?._id).filter((id) => id && id !== userId),
},
});
break;

@ -2,13 +2,12 @@ import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import mem from 'mem';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Rooms, Users } from '@rocket.chat/models';
import { Rooms, Users, Subscriptions } from '@rocket.chat/models';
import { Team } from '@rocket.chat/core-services';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings';
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
import { Subscriptions } from '../../app/models/server';
import { settings } from '../../app/settings/server';
import { getFederationDomain } from '../../app/federation/server/lib/getFederationDomain';
import { isFederationEnabled } from '../../app/federation/server/lib/isFederationEnabled';
@ -134,7 +133,7 @@ const getTeams = async (
return;
}
const userSubs: ISubscription[] = Subscriptions.cachedFindByUserId(user._id).fetch();
const userSubs = await Subscriptions.findByUserId(user._id).toArray();
const ids = userSubs.map((sub) => sub.rid);
const { cursor, totalCount } = Rooms.findPaginatedContainingNameOrFNameInIdsAsTeamMain(
searchTerm ? new RegExp(searchTerm, 'i') : null,

@ -2,12 +2,12 @@ import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import _ from 'underscore';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import type { IRoom, ISubscription } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';
import type { IRoom } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions } from '@rocket.chat/models';
import type { FindOptions } from 'mongodb';
import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission';
import { Subscriptions, Users } from '../../app/models/server';
import { Users } from '../../app/models/server';
import { getUserPreference } from '../../app/utils/server';
import { settings } from '../../app/settings/server';
import { trim } from '../../lib/utils/stringUtils';
@ -72,9 +72,7 @@ Meteor.methods<ServerMethods>({
channels = channels.concat(await Rooms.findByType('c', options).toArray());
}
} else if (await hasPermissionAsync(userId, 'view-joined-room')) {
const roomIds = Subscriptions.findByTypeAndUserId('c', userId, { fields: { rid: 1 } })
.fetch()
.map((s: ISubscription) => s.rid);
const roomIds = (await Subscriptions.findByTypeAndUserId('c', userId, { projection: { rid: 1 } }).toArray()).map((s) => s.rid);
if (filter) {
channels = channels.concat(await Rooms.findByTypeInIdsAndNameContaining('c', roomIds, filter, options).toArray());
} else {
@ -95,9 +93,7 @@ Meteor.methods<ServerMethods>({
const groupByType = userPref !== undefined ? userPref : settings.get('UI_Group_Channels_By_Type');
if (!groupByType) {
const roomIds = Subscriptions.findByTypeAndUserId('p', userId, { fields: { rid: 1 } })
.fetch()
.map((s: ISubscription) => s.rid);
const roomIds = (await Subscriptions.findByTypeAndUserId('p', userId, { projection: { rid: 1 } }).toArray()).map((s) => s.rid);
if (filter) {
channels = channels.concat(await Rooms.findByTypeInIdsAndNameContaining('p', roomIds, filter, options).toArray());
} else {

@ -1,11 +1,10 @@
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { Messages } from '@rocket.chat/models';
import { Messages, Subscriptions } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import type { ISubscription, IUser } from '@rocket.chat/core-typings';
import { canAccessRoomIdAsync } from '../../app/authorization/server/functions/canAccessRoom';
import { Subscriptions } from '../../app/models/server';
import { settings } from '../../app/settings/server';
import { readSecondaryPreferred } from '../database/readSecondaryPreferred';
import { parseMessageSearchQuery } from '../lib/parseMessageSearchQuery';
@ -73,11 +72,7 @@ Meteor.methods<ServerMethods>({
query.rid = rid;
} else {
query.rid = {
$in: user?._id
? Subscriptions.findByUserId(user._id)
.fetch()
.map((subscription: ISubscription) => subscription.rid)
: [],
$in: user?._id ? (await Subscriptions.findByUserId(user._id).toArray()).map((subscription: ISubscription) => subscription.rid) : [],
};
}

@ -1056,7 +1056,7 @@ export class RoomsRaw extends BaseRaw {
}
async findBySubscriptionUserId(userId, options) {
const data = (await Subscriptions.cachedFindByUserId(userId, { projection: { rid: 1 } }).toArray()).map((item) => item.rid);
const data = (await Subscriptions.findByUserId(userId, { projection: { rid: 1 } }).toArray()).map((item) => item.rid);
const query = {
_id: {

@ -924,6 +924,24 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
return this.find(query, options);
}
countByRoomIdAndRoles(roomId: string, roles: string[]): Promise<number> {
roles = ([] as string[]).concat(roles);
const query = {
rid: roomId,
roles: { $in: roles },
};
return this.col.countDocuments(query);
}
countByRoomId(roomId: string): Promise<number> {
const query = {
rid: roomId,
};
return this.col.countDocuments(query);
}
findByType(types: ISubscription['t'][], options?: FindOptions<ISubscription>): FindCursor<ISubscription> {
const query: Filter<ISubscription> = {
t: {

@ -1,8 +1,8 @@
import type { ISubscription } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import { Subscriptions } from '@rocket.chat/models';
import { Subscriptions } from '../../../app/models/server';
import { subscriptionFields } from '../../modules/watchers/publishFields';
declare module '@rocket.chat/ui-contexts' {
@ -13,33 +13,33 @@ declare module '@rocket.chat/ui-contexts' {
}
Meteor.methods<ServerMethods>({
'subscriptions/get'(updatedAt) {
async 'subscriptions/get'(updatedAt) {
const uid = Meteor.userId();
if (!uid) {
return [];
}
const options = { fields: subscriptionFields };
const options = { projection: subscriptionFields };
const records: ISubscription[] = Subscriptions.findByUserId(uid, options).fetch();
const records: ISubscription[] = await Subscriptions.findByUserId(uid, options).toArray();
if (updatedAt instanceof Date) {
return {
update: records.filter((record) => {
return record._updatedAt > updatedAt;
}),
remove: Subscriptions.trashFindDeletedAfter(
remove: await Subscriptions.trashFindDeletedAfter(
updatedAt,
{
'u._id': uid,
},
{
fields: {
projection: {
_id: 1,
_deletedAt: 1,
},
},
).fetch(),
).toArray(),
};
}

@ -596,7 +596,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
const [rooms, total] = await Promise.all([cursor.toArray(), totalCount]);
const roomData = getSubscribedRoomsForUserWithDetails(userId, false, teamRoomIds);
const roomData = await getSubscribedRoomsForUserWithDetails(userId, false, teamRoomIds);
const records = [];
for (const room of rooms) {

@ -227,4 +227,6 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
removeUnreadThreadByRoomIdAndUserId(rid: string, userId: string, tmid: string, clearAlert?: boolean): Promise<UpdateResult>;
removeUnreadThreadsByRoomId(rid: string, tunread: string[]): Promise<UpdateResult | Document>;
countByRoomIdAndRoles(roomId: string, roles: string[]): Promise<number>;
countByRoomId(roomId: string): Promise<number>;
}

Loading…
Cancel
Save