diff --git a/packages/rocketchat-action-links/both/lib/actionLinks.js b/packages/rocketchat-action-links/both/lib/actionLinks.js index edf0d0e4b257..b055555600c8 100644 --- a/packages/rocketchat-action-links/both/lib/actionLinks.js +++ b/packages/rocketchat-action-links/both/lib/actionLinks.js @@ -5,7 +5,8 @@ RocketChat.actionLinks = { RocketChat.actionLinks.actions[name] = funct; }, getMessage(name, messageId) { - if (!Meteor.userId()) { + const userId = Meteor.userId(); + if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: 'actionLinks.getMessage' }); } @@ -14,8 +15,11 @@ RocketChat.actionLinks = { throw new Meteor.Error('error-invalid-message', 'Invalid message', { function: 'actionLinks.getMessage' }); } - const room = RocketChat.models.Rooms.findOne({ _id: message.rid }); - if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + const subscription = RocketChat.models.Subscriptions.findOne({ + rid: message.rid, + 'u._id': userId + }); + if (!subscription) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { function: 'actionLinks.getMessage' }); } diff --git a/packages/rocketchat-api/server/api.js b/packages/rocketchat-api/server/api.js index c27b4d664226..fd0f09a1d3e2 100644 --- a/packages/rocketchat-api/server/api.js +++ b/packages/rocketchat-api/server/api.js @@ -9,10 +9,7 @@ class API extends Restivus { this.fieldSeparator = '.'; this.defaultFieldsToExclude = { joinCode: 0, - $loki: 0, - meta: 0, members: 0, - usernames: 0, // Please use the `channel/dm/group.members` endpoint. This is disabled for performance reasons importIds: 0 }; this.limitedUserFieldsToExclude = { @@ -85,13 +82,14 @@ class API extends Restivus { return result; } - failure(result, errorType) { + failure(result, errorType, stack) { if (_.isObject(result)) { result.success = false; } else { result = { success: false, - error: result + error: result, + stack }; if (errorType) { diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js index be7de10766be..3c13e3e94a3f 100644 --- a/packages/rocketchat-api/server/v1/channels.js +++ b/packages/rocketchat-api/server/v1/channels.js @@ -1,15 +1,12 @@ import _ from 'underscore'; //Returns the channel IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property -function findChannelByIdOrName({ params, checkedArchived = true, returnUsernames = false }) { +function findChannelByIdOrName({ params, checkedArchived = true }) { if ((!params.roomId || !params.roomId.trim()) && (!params.roomName || !params.roomName.trim())) { throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" or "roomName" is required'); } const fields = { ...RocketChat.API.v1.defaultFieldsToExclude }; - if (returnUsernames) { - delete fields.usernames; - } let room; if (params.roomId) { @@ -143,7 +140,7 @@ RocketChat.API.v1.addRoute('channels.close', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.counters', { authRequired: true }, { get() { const access = RocketChat.authz.hasPermission(this.userId, 'view-room-administration'); - const ruserId = this.requestParams().userId; + const userId = this.requestParams().userId; let user = this.userId; let unreads = null; let userMentions = null; @@ -152,34 +149,33 @@ RocketChat.API.v1.addRoute('channels.counters', { authRequired: true }, { let msgs = null; let latest = null; let members = null; - let lm = null; - if (ruserId) { + if (userId) { if (!access) { return RocketChat.API.v1.unauthorized(); } - user = ruserId; + user = userId; } const room = findChannelByIdOrName({ params: this.requestParams(), returnUsernames: true }); - const channel = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user); - lm = channel._room.lm ? channel._room.lm : channel._room._updatedAt; + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user); + const lm = room.lm ? room.lm : room._updatedAt; - if (typeof channel !== 'undefined' && channel.open) { - if (channel.ls) { - unreads = RocketChat.models.Messages.countVisibleByRoomIdBetweenTimestampsInclusive(channel.rid, channel.ls, lm); - unreadsFrom = channel.ls; + if (typeof subscription !== 'undefined' && subscription.open) { + if (subscription.ls) { + unreads = RocketChat.models.Messages.countVisibleByRoomIdBetweenTimestampsInclusive(subscription.rid, subscription.ls, lm); + unreadsFrom = subscription.ls; } - userMentions = channel.userMentions; + userMentions = subscription.userMentions; joined = true; } if (access || joined) { msgs = room.msgs; latest = lm; - members = room.usernames.length; + members = room.usersCount; } return RocketChat.API.v1.success({ @@ -494,28 +490,32 @@ RocketChat.API.v1.addRoute('channels.list', { authRequired: true }, { const { sort, fields, query } = this.parseJsonQuery(); const hasPermissionToSeeAllPublicChannels = RocketChat.authz.hasPermission(this.userId, 'view-c-room'); - const ourQuery = Object.assign({}, query, { t: 'c' }); + const ourQuery = { ...query, t: 'c' }; - if (RocketChat.authz.hasPermission(this.userId, 'view-joined-room') && !hasPermissionToSeeAllPublicChannels) { - ourQuery.usernames = { - $in: [this.user.username] - }; - } else if (!hasPermissionToSeeAllPublicChannels) { - return RocketChat.API.v1.unauthorized(); + if (!hasPermissionToSeeAllPublicChannels) { + if (!RocketChat.authz.hasPermission(this.userId, 'view-joined-room')) { + return RocketChat.API.v1.unauthorized(); + } + const roomIds = RocketChat.models.Subscriptions.findByUserIdAndType(this.userId, 'c', { fields: { rid: 1 } }).fetch().map(s => s.rid); + ourQuery._id = { $in: roomIds }; } - const rooms = RocketChat.models.Rooms.find(ourQuery, { + const cursor = RocketChat.models.Rooms.find(ourQuery, { sort: sort ? sort : { name: 1 }, skip: offset, limit: count, fields - }).fetch(); + }); + + const total = cursor.count(); + + const rooms = cursor.fetch(); return RocketChat.API.v1.success({ channels: rooms, count: rooms.length, offset, - total: RocketChat.models.Rooms.find(ourQuery).count() + total }); } } @@ -524,22 +524,19 @@ RocketChat.API.v1.addRoute('channels.list', { authRequired: true }, { RocketChat.API.v1.addRoute('channels.list.joined', { authRequired: true }, { get() { const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); - const ourQuery = Object.assign({}, query, { - t: 'c', - 'u._id': this.userId - }); - - let rooms = _.pluck(RocketChat.models.Subscriptions.find(ourQuery).fetch(), '_room'); - const totalCount = rooms.length; + const { sort, fields } = this.parseJsonQuery(); - rooms = RocketChat.models.Rooms.processQueryOptionsOnResult(rooms, { + // TODO: CACHE: Add Breacking notice since we removed the query param + const cursor = RocketChat.models.Rooms.findBySubscriptionTypeAndUserId('c', this.userId, { sort: sort ? sort : { name: 1 }, skip: offset, limit: count, fields }); + const totalCount = cursor.count(); + const rooms = cursor.fetch(); + return RocketChat.API.v1.success({ channels: rooms, offset, @@ -553,8 +550,7 @@ RocketChat.API.v1.addRoute('channels.members', { authRequired: true }, { get() { const findResult = findChannelByIdOrName({ params: this.requestParams(), - checkedArchived: false, - returnUsernames: true + checkedArchived: false }); if (findResult.broadcast && !RocketChat.authz.hasPermission(this.userId, 'view-broadcast-member-list')) { @@ -562,29 +558,29 @@ RocketChat.API.v1.addRoute('channels.members', { authRequired: true }, { } const { offset, count } = this.getPaginationItems(); - const { sort } = this.parseJsonQuery(); - - const shouldBeOrderedDesc = Match.test(sort, Object) && Match.test(sort.username, Number) && sort.username === -1; + const { sort = {} } = this.parseJsonQuery(); - let members = RocketChat.models.Rooms.processQueryOptionsOnResult(Array.from(findResult.usernames).sort(), { + const subscriptions = RocketChat.models.Subscriptions.findByRoomId(findResult._id, { + fields: { 'u._id': 1 }, + sort: { 'u.username': sort.username != null ? sort.username : 1 }, skip: offset, limit: count }); - if (shouldBeOrderedDesc) { - members = members.reverse(); - } + const total = subscriptions.count(); + + const members = subscriptions.fetch().map(s => s.u && s.u._id); - const users = RocketChat.models.Users.find({ username: { $in: members } }, { + const users = RocketChat.models.Users.find({ _id: { $in: members } }, { fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, - sort: sort ? sort : { username: 1 } + sort: { username: sort.username != null ? sort.username : 1 } }).fetch(); return RocketChat.API.v1.success({ members: users, count: users.length, offset, - total: findResult.usernames.length + total }); } }); @@ -593,8 +589,7 @@ RocketChat.API.v1.addRoute('channels.messages', { authRequired: true }, { get() { const findResult = findChannelByIdOrName({ params: this.requestParams(), - checkedArchived: false, - returnUsernames: true + checkedArchived: false }); const { offset, count } = this.getPaginationItems(); const { sort, fields, query } = this.parseJsonQuery(); @@ -602,27 +597,59 @@ RocketChat.API.v1.addRoute('channels.messages', { authRequired: true }, { const ourQuery = Object.assign({}, query, { rid: findResult._id }); //Special check for the permissions - if (RocketChat.authz.hasPermission(this.userId, 'view-joined-room') && !findResult.usernames.includes(this.user.username)) { + if (RocketChat.authz.hasPermission(this.userId, 'view-joined-room') && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId, { fields: { _id: 1 } })) { return RocketChat.API.v1.unauthorized(); - } else if (!RocketChat.authz.hasPermission(this.userId, 'view-c-room')) { + } + if (!RocketChat.authz.hasPermission(this.userId, 'view-c-room')) { return RocketChat.API.v1.unauthorized(); } - const messages = RocketChat.models.Messages.find(ourQuery, { + const cursor = RocketChat.models.Messages.find(ourQuery, { sort: sort ? sort : { ts: -1 }, skip: offset, limit: count, fields - }).fetch(); + }); + + const total = cursor.count(); + const messages = cursor.fetch(); return RocketChat.API.v1.success({ messages, count: messages.length, offset, - total: RocketChat.models.Messages.find(ourQuery).count() + total }); } }); +// TODO: CACHE: I dont like this method( functionality and how we implemented ) its very expensive +// TODO check if this code is better or not +// RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, { +// get() { +// const { query } = this.parseJsonQuery(); +// const ourQuery = Object.assign({}, query, { t: 'c' }); + +// const room = RocketChat.models.Rooms.findOne(ourQuery); + +// if (room == null) { +// return RocketChat.API.v1.failure('Channel does not exists'); +// } + +// const ids = RocketChat.models.Subscriptions.find({ rid: room._id }, { fields: { 'u._id': 1 } }).fetch().map(sub => sub.u._id); + +// const online = RocketChat.models.Users.find({ +// username: { $exists: 1 }, +// _id: { $in: ids }, +// status: { $in: ['online', 'away', 'busy'] } +// }, { +// fields: { username: 1 } +// }).fetch(); + +// return RocketChat.API.v1.success({ +// online +// }); +// } +// }); RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, { get() { @@ -636,14 +663,13 @@ RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, { } const online = RocketChat.models.Users.findUsersNotOffline({ - fields: { - username: 1 - } + fields: { username: 1 } }).fetch(); const onlineInRoom = []; online.forEach(user => { - if (room.usernames.indexOf(user.username) !== -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(root._id, user._id, { fields: { _id: 1 } }); + if (subscription) { onlineInRoom.push({ _id: user._id, username: user.username diff --git a/packages/rocketchat-api/server/v1/groups.js b/packages/rocketchat-api/server/v1/groups.js index ffeb3cf4434a..c5835e0f4099 100644 --- a/packages/rocketchat-api/server/v1/groups.js +++ b/packages/rocketchat-api/server/v1/groups.js @@ -120,7 +120,6 @@ RocketChat.API.v1.addRoute('groups.counters', { authRequired: true }, { let msgs = null; let latest = null; let members = null; - let lm = null; if ((!params.roomId || !params.roomId.trim()) && (!params.roomName || !params.roomName.trim())) { throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required'); @@ -146,22 +145,22 @@ RocketChat.API.v1.addRoute('groups.counters', { authRequired: true }, { } user = params.userId; } - const group = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user); - lm = group._room.lm ? group._room.lm : group._room._updatedAt; + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user); + const lm = room.lm ? room.lm : room._updatedAt; - if (typeof group !== 'undefined' && group.open) { - if (group.ls) { - unreads = RocketChat.models.Messages.countVisibleByRoomIdBetweenTimestampsInclusive(group.rid, group.ls, lm); - unreadsFrom = group.ls; + if (typeof subscription !== 'undefined' && subscription.open) { + if (subscription.ls) { + unreads = RocketChat.models.Messages.countVisibleByRoomIdBetweenTimestampsInclusive(subscription.rid, subscription.ls, lm); + unreadsFrom = subscription.ls; } - userMentions = group.userMentions; + userMentions = subscription.userMentions; joined = true; } if (access || joined) { msgs = room.msgs; latest = lm; - members = room.usernames.length; + members = room.usersCount; } return RocketChat.API.v1.success({ @@ -220,7 +219,7 @@ RocketChat.API.v1.addRoute('groups.delete', { authRequired: true }, { }); return RocketChat.API.v1.success({ - group: RocketChat.models.Rooms.processQueryOptionsOnResult([findResult._room], { fields: RocketChat.API.v1.defaultFieldsToExclude })[0] + group: RocketChat.models.Rooms.findOneById(findResult.rid, { fields: RocketChat.API.v1.defaultFieldsToExclude }) }); } }); @@ -400,22 +399,20 @@ RocketChat.API.v1.addRoute('groups.leave', { authRequired: true }, { RocketChat.API.v1.addRoute('groups.list', { authRequired: true }, { get() { const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); - const ourQuery = Object.assign({}, query, { - t: 'p', - 'u._id': this.userId - }); + const { sort, fields} = this.parseJsonQuery(); - let rooms = _.pluck(RocketChat.models.Subscriptions.find(ourQuery).fetch(), '_room'); - const totalCount = rooms.length; - - rooms = RocketChat.models.Rooms.processQueryOptionsOnResult(rooms, { + // TODO: CACHE: Add Breacking notice since we removed the query param + const cursor = RocketChat.models.Rooms.findBySubscriptionTypeAndUserId('p', this.userId, { sort: sort ? sort : { name: 1 }, skip: offset, limit: count, fields }); + const totalCount = cursor.count(); + const rooms = cursor.fetch(); + + return RocketChat.API.v1.success({ groups: rooms, offset, @@ -457,34 +454,36 @@ RocketChat.API.v1.addRoute('groups.listAll', { authRequired: true }, { RocketChat.API.v1.addRoute('groups.members', { authRequired: true }, { get() { const findResult = findPrivateGroupByIdOrName({ params: this.requestParams(), userId: this.userId }); + const room = RocketChat.models.Rooms.findOneById(findResult.rid, { fields: { broadcast: 1 } }); - if (findResult._room.broadcast && !RocketChat.authz.hasPermission(this.userId, 'view-broadcast-member-list')) { + if (room.broadcast && !RocketChat.authz.hasPermission(this.userId, 'view-broadcast-member-list')) { return RocketChat.API.v1.unauthorized(); } const { offset, count } = this.getPaginationItems(); - const { sort } = this.parseJsonQuery(); - - let sortFn = (a, b) => a > b; - if (Match.test(sort, Object) && Match.test(sort.username, Number) && sort.username === -1) { - sortFn = (a, b) => b < a; - } + const { sort = {} } = this.parseJsonQuery(); - const members = RocketChat.models.Rooms.processQueryOptionsOnResult(Array.from(findResult._room.usernames).sort(sortFn), { + const subscriptions = RocketChat.models.Subscriptions.findByRoomId(findResult.rid, { + fields: { 'u._id': 1 }, + sort: { 'u.username': sort.username != null ? sort.username : 1 }, skip: offset, limit: count }); - const users = RocketChat.models.Users.find({ username: { $in: members } }, { + const total = subscriptions.count(); + + const members = subscriptions.fetch().map(s => s.u && s.u._id); + + const users = RocketChat.models.Users.find({ _id: { $in: members } }, { fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, - sort: sort ? sort : { username: 1 } + sort: { username: sort.username != null ? sort.username : 1 } }).fetch(); return RocketChat.API.v1.success({ members: users, - count: members.length, + count: users.length, offset, - total: findResult._room.usernames.length + total }); } }); @@ -512,7 +511,7 @@ RocketChat.API.v1.addRoute('groups.messages', { authRequired: true }, { }); } }); - +// TODO: CACHE: same as channels.online RocketChat.API.v1.addRoute('groups.online', { authRequired: true }, { get() { const { query } = this.parseJsonQuery(); @@ -532,7 +531,8 @@ RocketChat.API.v1.addRoute('groups.online', { authRequired: true }, { const onlineInRoom = []; online.forEach(user => { - if (room.usernames.indexOf(user.username) !== -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(root._id, user._id, { fields: { _id: 1 } }); + if (subscription) { onlineInRoom.push({ _id: user._id, username: user.username diff --git a/packages/rocketchat-api/server/v1/im.js b/packages/rocketchat-api/server/v1/im.js index c2ad7d8da8e0..865b6ab3bc8a 100644 --- a/packages/rocketchat-api/server/v1/im.js +++ b/packages/rocketchat-api/server/v1/im.js @@ -1,5 +1,3 @@ -import _ from 'underscore'; - function findDirectMessageRoom(params, user) { if ((!params.roomId || !params.roomId.trim()) && (!params.username || !params.username.trim())) { throw new Meteor.Error('error-room-param-not-provided', 'Body param "roomId" or "username" is required'); @@ -86,7 +84,7 @@ RocketChat.API.v1.addRoute(['dm.counters', 'im.counters'], { authRequired: true if (access || joined) { msgs = room.msgs; latest = lm; - members = room.usernames.length; + members = room.usersCount; } return RocketChat.API.v1.success({ @@ -187,21 +185,26 @@ RocketChat.API.v1.addRoute(['dm.members', 'im.members'], { authRequired: true }, const { offset, count } = this.getPaginationItems(); const { sort } = this.parseJsonQuery(); - - const members = RocketChat.models.Rooms.processQueryOptionsOnResult(Array.from(findResult.room.usernames), { - sort: sort ? sort : -1, + const cursor = RocketChat.models.Subscriptions.findByRoomId(findResult._id, { + sort: { 'u.username': sort.username != null ? sort.username : 1 }, skip: offset, limit: count }); - const users = RocketChat.models.Users.find({ username: { $in: members } }, - { fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 } }).fetch(); + const total = cursor.count(); + + const members = cursor.fetch().map(s => s.u && s.u.username); + + const users = RocketChat.models.Users.find({ username: { $in: members } }, { + fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + sort: { username: sort.username != null ? sort.username : 1 } + }).fetch(); return RocketChat.API.v1.success({ members: users, count: members.length, offset, - total: findResult.room.usernames.length + total }); } }); @@ -275,27 +278,25 @@ RocketChat.API.v1.addRoute(['dm.messages.others', 'im.messages.others'], { authR RocketChat.API.v1.addRoute(['dm.list', 'im.list'], { authRequired: true }, { get() { const { offset, count } = this.getPaginationItems(); - const { sort, fields, query } = this.parseJsonQuery(); - const ourQuery = Object.assign({}, query, { - t: 'd', - 'u._id': this.userId - }); + const { sort = { name: 1 }, fields } = this.parseJsonQuery(); - let rooms = _.pluck(RocketChat.models.Subscriptions.find(ourQuery).fetch(), '_room'); - const totalCount = rooms.length; + // TODO: CACHE: Add Breacking notice since we removed the query param - rooms = RocketChat.models.Rooms.processQueryOptionsOnResult(rooms, { - sort: sort ? sort : { name: 1 }, + const cursor = RocketChat.models.Rooms.findBySubscriptionTypeAndUserId('d', this.userId, { + sort, skip: offset, limit: count, fields }); + const total = cursor.count(); + const rooms = cursor.fetch(); + return RocketChat.API.v1.success({ ims: rooms, offset, count: rooms.length, - total: totalCount + total }); } }); diff --git a/packages/rocketchat-api/server/v1/subscriptions.js b/packages/rocketchat-api/server/v1/subscriptions.js index e58fe4094a26..8bc1c57e4fdf 100644 --- a/packages/rocketchat-api/server/v1/subscriptions.js +++ b/packages/rocketchat-api/server/v1/subscriptions.js @@ -33,13 +33,7 @@ RocketChat.API.v1.addRoute('subscriptions.getOne', { authRequired: true }, { return RocketChat.API.v1.failure('The \'roomId\' param is required'); } - const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { - fields: { - _room: 0, - _user: 0, - $loki: 0 - } - }); + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId); return RocketChat.API.v1.success({ subscription diff --git a/packages/rocketchat-api/server/v1/users.js b/packages/rocketchat-api/server/v1/users.js index 496833312557..d64b5680aaa5 100644 --- a/packages/rocketchat-api/server/v1/users.js +++ b/packages/rocketchat-api/server/v1/users.js @@ -352,26 +352,29 @@ RocketChat.API.v1.addRoute('users.setPreferences', { authRequired: true }, { }) }); - let preferences; const userId = this.bodyParams.userId ? this.bodyParams.userId : this.userId; + const userData = { + _id: userId, + settings: { + preferences: this.bodyParams.data + } + }; + if (this.bodyParams.data.language) { const language = this.bodyParams.data.language; delete this.bodyParams.data.language; - preferences = _.extend({ _id: userId, settings: { preferences: this.bodyParams.data }, language }); - } else { - preferences = _.extend({ _id: userId, settings: { preferences: this.bodyParams.data } }); + userData.language = language; } - // Keep compatibility with old values - if (preferences.emailNotificationMode === 'all') { - preferences.emailNotificationMode = 'mentions'; - } else if (preferences.emailNotificationMode === 'disabled') { - preferences.emailNotificationMode = 'nothing'; - } - - Meteor.runAsUser(this.userId, () => RocketChat.saveUser(this.userId, preferences)); + Meteor.runAsUser(this.userId, () => RocketChat.saveUser(this.userId, userData)); - return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.bodyParams.userId, { fields: preferences }) }); + return RocketChat.API.v1.success({ + user: RocketChat.models.Users.findOneById(userId, { + fields: { + 'settings.preferences': 1 + } + }) + }); } }); diff --git a/packages/rocketchat-apps/server/bridges/messages.js b/packages/rocketchat-apps/server/bridges/messages.js index 22d391e5c581..126b1f1b668b 100644 --- a/packages/rocketchat-apps/server/bridges/messages.js +++ b/packages/rocketchat-apps/server/bridges/messages.js @@ -54,7 +54,7 @@ export class AppMessageBridge { async notifyRoom(room, message, appId) { console.log(`The App ${ appId } is notifying a room's users.`); - if (room && room.usernames && Array.isArray(room.usernames)) { + if (room) { const msg = this.orch.getConverters().get('messages').convertAppMessage(message); const rmsg = Object.assign(msg, { _id: Random.id(), @@ -64,12 +64,14 @@ export class AppMessageBridge { editor: undefined }); - room.usernames.forEach((u) => { - const user = RocketChat.models.Users.findOneByUsername(u); - if (user) { - RocketChat.Notifications.notifyUser(user._id, 'message', rmsg); - } - }); + const users = RocketChat.models.Subscriptions.findByRoomIdWhenUserIdExists(room._id, { fields: { 'u._id': 1 } }) + .fetch() + .map(s => s.u._id); + RocketChat.models.Users.findByIds(users, { fields: { _id: 1 } }) + .fetch() + .forEach(({ _id }) => + RocketChat.Notifications.notifyUser(_id, 'message', rmsg) + ); } } } diff --git a/packages/rocketchat-apps/server/bridges/rooms.js b/packages/rocketchat-apps/server/bridges/rooms.js index d0bece2d7e8e..2db168165233 100644 --- a/packages/rocketchat-apps/server/bridges/rooms.js +++ b/packages/rocketchat-apps/server/bridges/rooms.js @@ -24,7 +24,7 @@ export class AppRoomBridge { let rid; Meteor.runAsUser(room.creator.id, () => { - const info = Meteor.call(method, rcRoom.usernames); + const info = Meteor.call(method, rcRoom.members); rid = info.rid; }); diff --git a/packages/rocketchat-apps/server/converters/rooms.js b/packages/rocketchat-apps/server/converters/rooms.js index a12e9bcf0499..9fd875755331 100644 --- a/packages/rocketchat-apps/server/converters/rooms.js +++ b/packages/rocketchat-apps/server/converters/rooms.js @@ -37,7 +37,7 @@ export class AppRoomsConverter { name: room.slugifiedName, t: room.type, u, - usernames: room.usernames, + members: room.members, default: typeof room.isDefault === 'undefined' ? false : room.isDefault, ro: typeof room.isReadOnly === 'undefined' ? false : room.isReadOnly, sysMes: typeof room.displaySystemMessages === 'undefined' ? true : room.displaySystemMessages, @@ -64,7 +64,7 @@ export class AppRoomsConverter { slugifiedName: room.name, type: this._convertTypeToApp(room.t), creator, - usernames: room.usernames, + members: room.members, isDefault: typeof room.default === 'undefined' ? false : room.default, isReadOnly: typeof room.ro === 'undefined' ? false : room.ro, displaySystemMessages: typeof room.sysMes === 'undefined' ? true : room.sysMes, diff --git a/packages/rocketchat-authorization/server/functions/canAccessRoom.js b/packages/rocketchat-authorization/server/functions/canAccessRoom.js index 31a6e17674de..1084bb8ecf04 100644 --- a/packages/rocketchat-authorization/server/functions/canAccessRoom.js +++ b/packages/rocketchat-authorization/server/functions/canAccessRoom.js @@ -12,7 +12,7 @@ RocketChat.authz.roomAccessValidators = [ function(room, user = {}) { const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); if (subscription) { - return subscription._room; + return RocketChat.models.Rooms.findOneById(subscription.rid); } } ]; diff --git a/packages/rocketchat-authorization/server/models/Permissions.js b/packages/rocketchat-authorization/server/models/Permissions.js index a82e0411fca0..220e8d94a827 100644 --- a/packages/rocketchat-authorization/server/models/Permissions.js +++ b/packages/rocketchat-authorization/server/models/Permissions.js @@ -29,5 +29,4 @@ class ModelPermissions extends RocketChat.models._Base { } } -RocketChat.models.Permissions = new ModelPermissions('permissions', true); -RocketChat.models.Permissions.cache.load(); +RocketChat.models.Permissions = new ModelPermissions('permissions'); diff --git a/packages/rocketchat-authorization/server/models/Roles.js b/packages/rocketchat-authorization/server/models/Roles.js index 8c5f1fed85fa..f93c95883c82 100644 --- a/packages/rocketchat-authorization/server/models/Roles.js +++ b/packages/rocketchat-authorization/server/models/Roles.js @@ -65,5 +65,4 @@ class ModelRoles extends RocketChat.models._Base { } } -RocketChat.models.Roles = new ModelRoles('roles', true); -RocketChat.models.Roles.cache.load(); +RocketChat.models.Roles = new ModelRoles('roles'); diff --git a/packages/rocketchat-authorization/server/publications/permissions.js b/packages/rocketchat-authorization/server/publications/permissions.js index 029109db025c..1477c91940d7 100644 --- a/packages/rocketchat-authorization/server/publications/permissions.js +++ b/packages/rocketchat-authorization/server/publications/permissions.js @@ -1,6 +1,8 @@ Meteor.methods({ 'permissions/get'(updatedAt) { this.unblock(); + // TODO: should we return this for non logged users? + // TODO: we could cache this collection const records = RocketChat.models.Permissions.find().fetch(); @@ -17,7 +19,17 @@ Meteor.methods({ } }); +RocketChat.models.Permissions.on('change', ({clientAction, id, data}) => { + switch (clientAction) { + case 'updated': + case 'inserted': + data = data || RocketChat.models.Permissions.findOneById(id); + break; -RocketChat.models.Permissions.on('changed', (type, permission) => { - RocketChat.Notifications.notifyLoggedInThisInstance('permissions-changed', type, permission); + case 'removed': + data = { _id: id }; + break; + } + + RocketChat.Notifications.notifyLoggedInThisInstance('permissions-changed', clientAction, data); }); diff --git a/packages/rocketchat-cas/server/cas_server.js b/packages/rocketchat-cas/server/cas_server.js index 492700a134f3..c5e689b444f9 100644 --- a/packages/rocketchat-cas/server/cas_server.js +++ b/packages/rocketchat-cas/server/cas_server.js @@ -233,7 +233,6 @@ Accounts.registerLoginHandler(function(options) { } if (!RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, userId)) { - RocketChat.models.Rooms.addUsernameByName(room_name, result.username); RocketChat.models.Subscriptions.createWithRoomAndUser(room, user, { ts: new Date(), open: true, diff --git a/packages/rocketchat-channel-settings/client/views/channelSettings.js b/packages/rocketchat-channel-settings/client/views/channelSettings.js index 922a44c9d75c..b84796ea3491 100644 --- a/packages/rocketchat-channel-settings/client/views/channelSettings.js +++ b/packages/rocketchat-channel-settings/client/views/channelSettings.js @@ -503,9 +503,6 @@ Template.channelSettingsInfo.helpers({ topic() { return Template.instance().room.topic; }, - users() { - return Template.instance().room.usernames; - }, channelIcon() { const roomType = Template.instance().room.t; diff --git a/packages/rocketchat-channel-settings/server/models/Rooms.js b/packages/rocketchat-channel-settings/server/models/Rooms.js index 07e81e5ff17f..de14f8c365c8 100644 --- a/packages/rocketchat-channel-settings/server/models/Rooms.js +++ b/packages/rocketchat-channel-settings/server/models/Rooms.js @@ -16,27 +16,27 @@ RocketChat.models.Rooms.setReadOnlyById = function(_id, readOnly) { }; const update = { $set: { - ro: readOnly + ro: readOnly, + muted: [] } }; if (readOnly) { - RocketChat.models.Subscriptions.findByRoomId(_id).forEach(function(subscription) { - if (subscription._user == null) { + RocketChat.models.Subscriptions.findByRoomIdWhenUsernameExists(_id, { fields: { 'u._id': 1, 'u.username': 1 } }).forEach(function({ u: user }) { + if (RocketChat.authz.hasPermission(user._id, 'post-readonly')) { return; } - const user = subscription._user; - if (RocketChat.authz.hasPermission(user._id, 'post-readonly') === false) { - if (!update.$set.muted) { - update.$set.muted = []; - } - return update.$set.muted.push(user.username); - } + return update.$set.muted.push(user.username); }); } else { update.$unset = { muted: '' }; } + + if (update.$set.muted.length === 0) { + delete update.$set.muted; + } + return this.update(query, update); }; diff --git a/packages/rocketchat-graphql/server/resolvers/channels/Channel-type.js b/packages/rocketchat-graphql/server/resolvers/channels/Channel-type.js index e16dffa8ab83..423ac8e7813e 100644 --- a/packages/rocketchat-graphql/server/resolvers/channels/Channel-type.js +++ b/packages/rocketchat-graphql/server/resolvers/channels/Channel-type.js @@ -14,9 +14,10 @@ const resolver = { return root.name; }, members: (root) => { - return root.usernames.map( - username => RocketChat.models.Users.findOneByUsername(username) - ); + const ids = RocketChat.models.Subscriptions.findByRoomIdWhenUserIdExists(root._id, { fields: { 'u._id': 1 } }) + .fetch() + .map(sub => sub.u._id); + return RocketChat.models.Users.findByIds(ids).fetch(); }, owners: (root) => { // there might be no owner @@ -26,7 +27,9 @@ const resolver = { return [RocketChat.models.Users.findOneByUsername(root.u.username)]; }, - numberOfMembers: (root) => (root.usernames || []).length, + numberOfMembers: (root) => { + return RocketChat.models.Subscriptions.findByRoomId(root._id).count(); + }, numberOfMessages: property('msgs'), readOnly: (root) => root.ro === true, direct: (root) => root.t === 'd', diff --git a/packages/rocketchat-graphql/server/resolvers/channels/channelsByUser.js b/packages/rocketchat-graphql/server/resolvers/channels/channelsByUser.js index 4fdc6e15a309..2b46164bf620 100644 --- a/packages/rocketchat-graphql/server/resolvers/channels/channelsByUser.js +++ b/packages/rocketchat-graphql/server/resolvers/channels/channelsByUser.js @@ -13,7 +13,8 @@ const resolver = { throw new Error('No user'); } - const rooms = RocketChat.models.Rooms.findByContainingUsername(user.username, { + const roomIds = RocketChat.models.Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch().map(s => s.rid); + const rooms = RocketChat.models.Rooms.findByIds(roomIds, { sort: { name: 1 }, diff --git a/packages/rocketchat-graphql/server/resolvers/users/User-type.js b/packages/rocketchat-graphql/server/resolvers/users/User-type.js index d768b78ab7f8..ee4df2393346 100644 --- a/packages/rocketchat-graphql/server/resolvers/users/User-type.js +++ b/packages/rocketchat-graphql/server/resolvers/users/User-type.js @@ -21,7 +21,7 @@ const resolver = { return await RocketChat.models.Rooms.findBySubscriptionUserId(_id).fetch(); }), directMessages: ({ username }) => { - return RocketChat.models.Rooms.findByTypeContainingUsername('d', username).fetch(); + return RocketChat.models.Rooms.findDirectRoomContainingUsername(username).fetch(); } } }; diff --git a/packages/rocketchat-integrations/server/lib/triggerHandler.js b/packages/rocketchat-integrations/server/lib/triggerHandler.js index 36a9b4777634..0894f3fb1ea3 100644 --- a/packages/rocketchat-integrations/server/lib/triggerHandler.js +++ b/packages/rocketchat-integrations/server/lib/triggerHandler.js @@ -89,12 +89,11 @@ RocketChat.integrations.triggerHandler = new class RocketChatIntegrationHandler history.data = { ...data }; if (data.user) { - history.data.user = _.omit(data.user, ['meta', '$loki', 'services']); + history.data.user = _.omit(data.user, ['services']); } if (data.room) { - history.data.room = _.omit(data.room, ['meta', '$loki', 'usernames']); - history.data.room.usernames = ['this_will_be_filled_in_with_usernames_when_replayed']; + history.data.room = data.room; } } diff --git a/packages/rocketchat-integrations/server/lib/validation.js b/packages/rocketchat-integrations/server/lib/validation.js index 22f83b80a780..22c06a8ce6dd 100644 --- a/packages/rocketchat-integrations/server/lib/validation.js +++ b/packages/rocketchat-integrations/server/lib/validation.js @@ -70,7 +70,7 @@ function _verifyUserHasPermissionForChannels(integration, userId, channels) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { function: 'validateOutgoing._verifyUserHasPermissionForChannels' }); } - if (record.usernames && !RocketChat.authz.hasPermission(userId, 'manage-integrations') && RocketChat.authz.hasPermission(userId, 'manage-own-integrations') && !record.usernames.includes(Meteor.user().username)) { + if (!RocketChat.authz.hasAllPermission(userId, 'manage-integrations', 'manage-own-integrations') && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(record._id, userId, { fields: { _id: 1 } })) { throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', { function: 'validateOutgoing._verifyUserHasPermissionForChannels' }); } } diff --git a/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.js b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.js index ffababb1d137..c933d9d4de87 100644 --- a/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.js +++ b/packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.js @@ -70,7 +70,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'addIncomingIntegration' }); } - if (record.usernames && !RocketChat.authz.hasPermission(this.userId, 'manage-integrations') && RocketChat.authz.hasPermission(this.userId, 'manage-own-integrations') && !record.usernames.includes(Meteor.user().username)) { + if (!RocketChat.authz.hasAllPermission(this.userId, 'manage-integrations', 'manage-own-integrations') && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(record._id, this.userId, { fields: { _id: 1 } })) { throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', { method: 'addIncomingIntegration' }); } } diff --git a/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.js b/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.js index bb789b816ad8..789c12e3bdaf 100644 --- a/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.js +++ b/packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.js @@ -72,7 +72,7 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'updateIncomingIntegration' }); } - if (record.usernames && !RocketChat.authz.hasPermission(this.userId, 'manage-integrations') && RocketChat.authz.hasPermission(this.userId, 'manage-own-integrations') && !record.usernames.includes(Meteor.user().username)) { + if (!RocketChat.authz.hasAllPermission(this.userId, 'manage-integrations', 'manage-own-integrations') && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(record._id, this.userId, { fields: { _id: 1 } })) { throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', { method: 'updateIncomingIntegration' }); } } diff --git a/packages/rocketchat-integrations/server/processWebhookMessage.js b/packages/rocketchat-integrations/server/processWebhookMessage.js index 91b3a85f55ab..76aa1e73a672 100644 --- a/packages/rocketchat-integrations/server/processWebhookMessage.js +++ b/packages/rocketchat-integrations/server/processWebhookMessage.js @@ -37,7 +37,7 @@ this.processWebhookMessage = function(messageObj, user, defaultValues = { channe throw new Meteor.Error('invalid-channel'); } - if (mustBeJoined && !room.usernames.includes(user.username)) { + if (mustBeJoined && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { fields: { _id: 1 } })) { // throw new Meteor.Error('invalid-room', 'Invalid room provided to send a message to, must be joined.'); throw new Meteor.Error('invalid-channel'); // Throwing the generic one so people can't "brute force" find rooms } diff --git a/packages/rocketchat-lib/client/lib/cachedCollection.js b/packages/rocketchat-lib/client/lib/cachedCollection.js index d1f6b3484f96..4daa5d35467f 100644 --- a/packages/rocketchat-lib/client/lib/cachedCollection.js +++ b/packages/rocketchat-lib/client/lib/cachedCollection.js @@ -213,7 +213,6 @@ class CachedCollection { Meteor.call(this.methodName, (error, data) => { this.log(`${ data.length } records loaded from server`); data.forEach((record) => { - delete record.$loki; RocketChat.callbacks.run(`cachedCollection-loadFromServer-${ this.name }`, record, 'changed'); this.collection.upsert({ _id: record._id }, _.omit(record, '_id')); @@ -276,7 +275,6 @@ class CachedCollection { }); for (const record of changes) { - delete record.$loki; RocketChat.callbacks.run(`cachedCollection-sync-${ this.name }`, record, record._deletedAt? 'removed' : 'changed'); if (record._deletedAt) { this.collection.remove({ _id: record._id }); @@ -342,7 +340,6 @@ class CachedCollection { this.collection.remove(record._id); RoomManager.close(record.t+record.name); } else { - delete record.$loki; this.collection.upsert({ _id: record._id }, _.omit(record, '_id')); } diff --git a/packages/rocketchat-lib/client/lib/openRoom.js b/packages/rocketchat-lib/client/lib/openRoom.js index 3ca566a57cc4..09dac262d5ea 100644 --- a/packages/rocketchat-lib/client/lib/openRoom.js +++ b/packages/rocketchat-lib/client/lib/openRoom.js @@ -42,7 +42,6 @@ function openRoom(type, name) { Session.set('roomNotFound', {type, name}); return BlazeLayout.render('main', {center: 'roomNotFound'}); } else { - delete record.$loki; RocketChat.models.Rooms.upsert({ _id: record._id }, _.omit(record, '_id')); RoomManager.close(type + name); return openRoom(type, name); diff --git a/packages/rocketchat-lib/package.js b/packages/rocketchat-lib/package.js index 4592252e5633..e1d0cec3e599 100644 --- a/packages/rocketchat-lib/package.js +++ b/packages/rocketchat-lib/package.js @@ -141,9 +141,6 @@ Package.onUse(function(api) { api.addFiles('server/startup/statsTracker.js', 'server'); - // CACHE - api.addFiles('server/startup/cache/CacheLoad.js', 'server'); - // SERVER PUBLICATIONS api.addFiles('server/publications/settings.js', 'server'); diff --git a/packages/rocketchat-lib/server/functions/Notifications.js b/packages/rocketchat-lib/server/functions/Notifications.js index 808d0b8bb902..7e0e09f4b7ad 100644 --- a/packages/rocketchat-lib/server/functions/Notifications.js +++ b/packages/rocketchat-lib/server/functions/Notifications.js @@ -27,11 +27,6 @@ RocketChat.Notifications = new class { this.streamLogged.allowRead('logged'); this.streamRoom.allowRead(function(eventName, extraData) { const [roomId] = eventName.split('/'); - const user = Meteor.users.findOne(this.userId, { - fields: { - username: 1 - } - }); const room = RocketChat.models.Rooms.findOneById(roomId); if (!room) { console.warn(`Invalid streamRoom eventName: "${ eventName }"`); @@ -43,7 +38,8 @@ RocketChat.Notifications = new class { if (this.userId == null) { return false; } - return room.usernames.indexOf(user.username) > -1; + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); + return subscription != null; }); this.streamRoomUsers.allowRead('none'); this.streamUser.allowRead(function(eventName) { diff --git a/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js b/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js index 414e5e3b7588..71718be0b84c 100644 --- a/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js +++ b/packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js @@ -5,7 +5,9 @@ RocketChat.addUserToDefaultChannels = function(user, silenced) { // put user in default rooms const muted = room.ro && !RocketChat.authz.hasPermission(user._id, 'post-readonly'); - RocketChat.models.Rooms.addUsernameById(room._id, user.username, muted); + if (muted) { + RocketChat.models.Rooms.muteUsernameByRoomId(room._id, user.username); + } if (!RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id)) { diff --git a/packages/rocketchat-lib/server/functions/addUserToRoom.js b/packages/rocketchat-lib/server/functions/addUserToRoom.js index 527b68fab3f8..42a8823c6edc 100644 --- a/packages/rocketchat-lib/server/functions/addUserToRoom.js +++ b/packages/rocketchat-lib/server/functions/addUserToRoom.js @@ -13,7 +13,10 @@ RocketChat.addUserToRoom = function(rid, user, inviter, silenced) { } const muted = room.ro && !RocketChat.authz.hasPermission(user._id, 'post-readonly'); - RocketChat.models.Rooms.addUsernameById(rid, user.username, muted); + if (muted) { + RocketChat.models.Rooms.muteUsernameByRoomId(rid, user.username); + } + RocketChat.models.Subscriptions.createWithRoomAndUser(room, user, { ts: now, open: true, diff --git a/packages/rocketchat-lib/server/functions/createRoom.js b/packages/rocketchat-lib/server/functions/createRoom.js index 0aa21c9e96dd..385b4ef43a8e 100644 --- a/packages/rocketchat-lib/server/functions/createRoom.js +++ b/packages/rocketchat-lib/server/functions/createRoom.js @@ -31,7 +31,7 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData fname: name, t: type, msgs: 0, - usernames: members, + usersCount: 0, u: { _id: owner._id, username: owner.username @@ -42,6 +42,10 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData sysMes: readOnly !== true }); + if (type === 'd') { + room.usernames = members; + } + if (Apps && Apps.isLoaded()) { const prevent = Promise.await(Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreatePrevent', room)); if (prevent) { diff --git a/packages/rocketchat-lib/server/functions/deleteUser.js b/packages/rocketchat-lib/server/functions/deleteUser.js index 362dc83a5014..5a65b0fae5fb 100644 --- a/packages/rocketchat-lib/server/functions/deleteUser.js +++ b/packages/rocketchat-lib/server/functions/deleteUser.js @@ -19,7 +19,7 @@ RocketChat.deleteUser = function(userId) { RocketChat.models.Subscriptions.db.findByUserId(userId).forEach((subscription) => { const room = RocketChat.models.Rooms.findOneById(subscription.rid); if (room) { - if (room.t !== 'c' && room.usernames.length === 1) { + if (room.t !== 'c' && RocketChat.models.Subscriptions.findByRoomId(room._id).count() === 1) { RocketChat.models.Rooms.removeById(subscription.rid); // Remove non-channel rooms with only 1 user (the one being deleted) } if (room.t === 'd') { @@ -30,8 +30,7 @@ RocketChat.deleteUser = function(userId) { }); RocketChat.models.Subscriptions.removeByUserId(userId); // Remove user subscriptions - RocketChat.models.Rooms.removeByTypeContainingUsername('d', user.username); // Remove direct rooms with the user - RocketChat.models.Rooms.removeUsernameFromAll(user.username); // Remove user from all other rooms + RocketChat.models.Rooms.removeDirectRoomContainingUsername(user.username); // Remove direct rooms with the user // removes user's avatar if (user.avatarOrigin === 'upload' || user.avatarOrigin === 'url') { diff --git a/packages/rocketchat-lib/server/functions/removeUserFromRoom.js b/packages/rocketchat-lib/server/functions/removeUserFromRoom.js index 87b9faf5d086..dc910a0a7ab1 100644 --- a/packages/rocketchat-lib/server/functions/removeUserFromRoom.js +++ b/packages/rocketchat-lib/server/functions/removeUserFromRoom.js @@ -3,9 +3,10 @@ RocketChat.removeUserFromRoom = function(rid, user) { if (room) { RocketChat.callbacks.run('beforeLeaveRoom', user, room); - RocketChat.models.Rooms.removeUsernameById(rid, user.username); - if (room.usernames.indexOf(user.username) !== -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { fields: { _id: 1 } }); + + if (subscription) { const removedUser = user; RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(rid, removedUser); } @@ -17,6 +18,7 @@ RocketChat.removeUserFromRoom = function(rid, user) { RocketChat.models.Subscriptions.removeByRoomIdAndUserId(rid, user._id); Meteor.defer(function() { + // TODO: CACHE: maybe a queue? RocketChat.callbacks.run('afterLeaveRoom', user, room); }); } diff --git a/packages/rocketchat-lib/server/functions/sendMessage.js b/packages/rocketchat-lib/server/functions/sendMessage.js index 321d085641f0..ff3bc8bef8ae 100644 --- a/packages/rocketchat-lib/server/functions/sendMessage.js +++ b/packages/rocketchat-lib/server/functions/sendMessage.js @@ -91,15 +91,6 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) { message.ts = new Date(); } - if (!room.usernames || room.usernames.length === 0) { - const updated_room = RocketChat.models.Rooms.findOneById(room._id); - if (updated_room) { - room = updated_room; - } else { - room.usernames = []; - } - } - if (RocketChat.settings.get('Message_Read_Receipt_Enabled')) { message.unread = true; } diff --git a/packages/rocketchat-lib/server/functions/setRealName.js b/packages/rocketchat-lib/server/functions/setRealName.js index da7be9783ca0..8781bfb15658 100644 --- a/packages/rocketchat-lib/server/functions/setRealName.js +++ b/packages/rocketchat-lib/server/functions/setRealName.js @@ -17,6 +17,8 @@ RocketChat._setRealName = function(userId, name) { RocketChat.models.Users.setName(user._id, name); user.name = name; + RocketChat.models.Subscriptions.updateDirectFNameByName(user.username, name); + if (RocketChat.settings.get('UI_Use_Real_Name') === true) { RocketChat.Notifications.notifyLogged('Users:NameChanged', { _id: user._id, diff --git a/packages/rocketchat-lib/server/lib/debug.js b/packages/rocketchat-lib/server/lib/debug.js index 7b0631970320..faf36b1e4a92 100644 --- a/packages/rocketchat-lib/server/lib/debug.js +++ b/packages/rocketchat-lib/server/lib/debug.js @@ -46,7 +46,11 @@ const traceConnection = (enable, filter, prefix, name, connection, userId) => { const wrapMethods = function(name, originalHandler, methodsMap) { methodsMap[name] = function() { traceConnection(Log_Trace_Methods, Log_Trace_Methods_Filter, 'method', name, this.connection, this.userId); - const end = RocketChat.metrics.meteorMethods.startTimer({method: name}); + const end = RocketChat.metrics.meteorMethods.startTimer({ + method: name, + has_connection: this.connection != null, + has_user: this.userId != null + }); const args = name === 'ufsWrite' ? Array.prototype.slice.call(arguments, 1) : arguments; logger.method(name, '-> userId:', Meteor.userId(), ', arguments: ', args); diff --git a/packages/rocketchat-lib/server/lib/metrics.js b/packages/rocketchat-lib/server/lib/metrics.js index 61ac0298bd2b..af838ba23d18 100644 --- a/packages/rocketchat-lib/server/lib/metrics.js +++ b/packages/rocketchat-lib/server/lib/metrics.js @@ -13,7 +13,7 @@ RocketChat.metrics = {}; RocketChat.metrics.meteorMethods = new client.Summary({ name: 'rocketchat_meteor_methods', help: 'summary of meteor methods count and time', - labelNames: ['method'] + labelNames: ['method', 'has_connection', 'has_user'] }); RocketChat.metrics.rocketchatCallbacks = new client.Summary({ diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index 58ac97fe91f3..ab951b839b31 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -174,7 +174,8 @@ function sendAllNotifications(message, room) { // Don't fetch all users if room exceeds max members const maxMembersForNotification = RocketChat.settings.get('Notifications_Max_Room_Members'); - const disableAllMessageNotifications = room.usernames && room.usernames.length > maxMembersForNotification && maxMembersForNotification !== 0; + const roomMembersCount = RocketChat.models.Subscriptions.findByRoomId(room._id).count(); + const disableAllMessageNotifications = roomMembersCount > maxMembersForNotification && maxMembersForNotification !== 0; const query = { rid: room._id, @@ -236,29 +237,23 @@ function sendAllNotifications(message, room) { // on public channels, if a mentioned user is not member of the channel yet, he will first join the channel and then be notified based on his preferences. if (room.t === 'c') { - Promise.all(message.mentions - .filter(({ _id, username }) => _id !== 'here' && _id !== 'all' && !room.usernames.includes(username)) - .map(async(user) => { - await callJoinRoom(user, room._id); - - return user._id; - }) - ).then((users) => { - users.forEach((userId) => { - const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, userId); - - sendNotification({ - subscription, - sender, - hasMentionToAll, - hasMentionToHere, - message, - notificationMessage, - room, - mentionIds - }); - }); - }); + const mentions = message.mentions.filter(({ _id }) => _id !== 'here' && _id !== 'all').map(({ _id }) => _id); + Promise.all(RocketChat.models.Subscriptions.findByRoomIdAndUserIds(room._id, mentions) + .fetch() + .map(async subscription => { + await callJoinRoom(subscription.u, room._id); + return subscription; + })).then(subscriptions => subscriptions.forEach(subscription => + sendNotification({ + subscription, + sender, + hasMentionToAll, + hasMentionToHere, + message, + notificationMessage, + room, + mentionIds + }))); } return message; diff --git a/packages/rocketchat-lib/server/methods/addUsersToRoom.js b/packages/rocketchat-lib/server/methods/addUsersToRoom.js index b00c42350d31..5a85068bdc06 100644 --- a/packages/rocketchat-lib/server/methods/addUsersToRoom.js +++ b/packages/rocketchat-lib/server/methods/addUsersToRoom.js @@ -16,8 +16,8 @@ Meteor.methods({ // Get user and room details const room = RocketChat.models.Rooms.findOneById(data.rid); const userId = Meteor.userId(); - const user = Meteor.user(); - const userInRoom = Array.isArray(room.usernames) && room.usernames.includes(user.username); + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(data.rid, userId, { fields: { _id: 1 } }); + const userInRoom = subscription != null; // Can't add to direct room ever if (room.t === 'd') { @@ -51,6 +51,7 @@ Meteor.methods({ } // Validate each user, then add to room + const user = Meteor.user(); data.users.forEach((username) => { const newUser = RocketChat.models.Users.findOneByUsername(username); if (!newUser) { @@ -58,7 +59,6 @@ Meteor.methods({ method: 'addUsersToRoom' }); } - RocketChat.addUserToRoom(data.rid, newUser, user); }); diff --git a/packages/rocketchat-lib/server/methods/getChannelHistory.js b/packages/rocketchat-lib/server/methods/getChannelHistory.js index b3583cb253ea..a8039a7594f6 100644 --- a/packages/rocketchat-lib/server/methods/getChannelHistory.js +++ b/packages/rocketchat-lib/server/methods/getChannelHistory.js @@ -15,7 +15,7 @@ Meteor.methods({ } //Make sure they can access the room - if (room.t === 'c' && !RocketChat.authz.hasPermission(fromUserId, 'preview-c-room') && room.usernames.indexOf(room.username) === -1) { + if (room.t === 'c' && !RocketChat.authz.hasPermission(fromUserId, 'preview-c-room') && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, fromUserId, { fields: { _id: 1 } })) { return false; } diff --git a/packages/rocketchat-lib/server/methods/leaveRoom.js b/packages/rocketchat-lib/server/methods/leaveRoom.js index dd13f2e67ba0..c85a44e1fefb 100644 --- a/packages/rocketchat-lib/server/methods/leaveRoom.js +++ b/packages/rocketchat-lib/server/methods/leaveRoom.js @@ -16,18 +16,19 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'leaveRoom' }); } - if (!Array.from(room.usernames || []).includes(user.username)) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { fields: { _id: 1 } }); + if (!subscription) { throw new Meteor.Error('error-user-not-in-room', 'You are not in this room', { method: 'leaveRoom' }); } // If user is room owner, check if there are other owners. If there isn't anyone else, warn user to set a new owner. if (RocketChat.authz.hasRole(user._id, 'owner', room._id)) { - const numOwners = RocketChat.authz.getUsersInRole('owner', room._id).fetch().length; + const numOwners = RocketChat.authz.getUsersInRole('owner', room._id).count(); if (numOwners === 1) { throw new Meteor.Error('error-you-are-last-owner', 'You are the last owner. Please set new owner before leaving the room.', { method: 'leaveRoom' }); } } - return RocketChat.removeUserFromRoom(rid, Meteor.user()); + return RocketChat.removeUserFromRoom(rid, user); } }); diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js index 09b04176d229..b714069c78fc 100644 --- a/packages/rocketchat-lib/server/models/Rooms.js +++ b/packages/rocketchat-lib/server/models/Rooms.js @@ -7,13 +7,8 @@ class ModelRooms extends RocketChat.models._Base { this.tryEnsureIndex({ 'name': 1 }, { unique: 1, sparse: 1 }); this.tryEnsureIndex({ 'default': 1 }); - this.tryEnsureIndex({ 'usernames': 1 }); this.tryEnsureIndex({ 't': 1 }); this.tryEnsureIndex({ 'u._id': 1 }); - - this.cache.ignoreUpdatedFields = ['msgs', 'lm']; - this.cache.ensureIndex(['t', 'name'], 'unique'); - this.cache.options = {fields: {usernames: 0}}; } findOneByIdOrName(_idOrName, options) { @@ -64,28 +59,6 @@ class ModelRooms extends RocketChat.models._Base { return this.findOne(query, options); } - findOneByIdContainingUsername(_id, username, options) { - const query = { - _id, - usernames: username - }; - - return this.findOne(query, options); - } - - findOneByNameAndTypeNotContainingUsername(name, type, username, options) { - const query = { - name, - t: type, - usernames: { - $ne: username - } - }; - - return this.findOne(query, options); - } - - // FIND findWithUsername(username, options) { @@ -106,6 +79,17 @@ class ModelRooms extends RocketChat.models._Base { return this.find(query, options); } + findByTypeInIds(type, ids, options) { + const query = { + _id: { + $in: ids + }, + t: type + }; + + return this.find(query, options); + } + findByTypes(types, options) { const query = { t: { @@ -123,23 +107,24 @@ class ModelRooms extends RocketChat.models._Base { } findBySubscriptionUserId(userId, options) { - let data; - if (this.useCache) { - data = RocketChat.models.Subscriptions.findByUserId(userId).fetch(); - data = data.map(function(item) { - if (item._room) { - return item._room; - } - console.log('Empty Room for Subscription', item); - }); - data = data.filter(item => item); - return this.arrayToCursor(this.processQueryOptionsOnResult(data, options)); - } + const data = RocketChat.models.Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch() + .map(item => item.rid); + + const query = { + _id: { + $in: data + } + }; + + return this.find(query, options); + } - data = RocketChat.models.Subscriptions.findByUserId(userId, {fields: {rid: 1}}).fetch(); - data = data.map(item => item.rid); + findBySubscriptionTypeAndUserId(type, userId, options) { + const data = RocketChat.models.Subscriptions.findByUserIdAndType(userId, type, { fields: { rid: 1 } }).fetch() + .map(item => item.rid); const query = { + t: type, _id: { $in: data } @@ -149,20 +134,8 @@ class ModelRooms extends RocketChat.models._Base { } findBySubscriptionUserIdUpdatedAfter(userId, _updatedAt, options) { - if (this.useCache) { - let data = RocketChat.models.Subscriptions.findByUserId(userId).fetch(); - data = data.map(function(item) { - if (item._room) { - return item._room; - } - console.log('Empty Room for Subscription', item); - }); - data = data.filter(item => item && item._updatedAt > _updatedAt); - return this.arrayToCursor(this.processQueryOptionsOnResult(data, options)); - } - - let ids = RocketChat.models.Subscriptions.findByUserId(userId, {fields: {rid: 1}}).fetch(); - ids = ids.map(item => item.rid); + const ids = RocketChat.models.Subscriptions.findByUserId(userId, { fields: { rid: 1 } }).fetch() + .map(item => item.rid); const query = { _id: { @@ -192,45 +165,6 @@ class ModelRooms extends RocketChat.models._Base { return this.find(query, options); } - findByNameContainingTypesWithUsername(name, types, options) { - const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); - - const $or = []; - for (const type of Array.from(types)) { - const obj = {name: nameRegex, t: type.type}; - if (type.username != null) { - obj.usernames = type.username; - } - if (type.ids != null) { - obj._id = {$in: type.ids}; - } - $or.push(obj); - } - - const query = {$or}; - - return this.find(query, options); - } - - findContainingTypesWithUsername(types, options) { - - const $or = []; - for (const type of Array.from(types)) { - const obj = {t: type.type}; - if (type.username != null) { - obj.usernames = type.username; - } - if (type.ids != null) { - obj._id = {$in: type.ids}; - } - $or.push(obj); - } - - const query = {$or}; - - return this.find(query, options); - } - findByNameContainingAndTypes(name, types, options) { const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); @@ -273,35 +207,29 @@ class ModelRooms extends RocketChat.models._Base { return this._db.find(query, options); } - findByNameAndTypesNotContainingUsername(name, types, username, options) { + findByNameAndTypesNotInIds(name, types, ids, options) { const query = { + _id: { + $ne: ids + }, t: { $in: types }, - name, - usernames: { - $ne: username - } + name }; // do not use cache return this._db.find(query, options); } - findByNameStartingAndTypes(name, types, options) { + findChannelAndPrivateByNameStarting(name, options) { const nameRegex = new RegExp(`^${ s.trim(s.escapeRegExp(name)) }`, 'i'); const query = { t: { - $in: types + $in: ['c', 'p'] }, - $or: [ - {name: nameRegex}, - { - t: 'd', - usernames: nameRegex - } - ] + name: nameRegex }; return this.find(query, options); @@ -318,62 +246,44 @@ class ModelRooms extends RocketChat.models._Base { return this.find(query, options); } - findByTypeContainingUsername(type, username, options) { + findDirectRoomContainingUsername(username, options) { const query = { - t: type, + t: 'd', usernames: username }; return this.find(query, options); } - findByTypeContainingUsernames(type, username, options) { - const query = { - t: type, - usernames: { $all: [].concat(username) } - }; - - return this.find(query, options); - } - - findByTypesAndNotUserIdContainingUsername(types, userId, username, options) { + findByTypeAndName(type, name, options) { const query = { - t: { - $in: types - }, - uid: { - $ne: userId - }, - usernames: username + name, + t: type }; return this.find(query, options); } - findByContainingUsername(username, options) { - const query = {usernames: username}; - - return this.find(query, options); - } - - findByTypeAndName(type, name, options) { - if (this.useCache) { - return this.cache.findByIndex('t,name', [type, name], options); - } + findByTypeAndNameContaining(type, name, options) { + const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); const query = { - name, + name: nameRegex, t: type }; return this.find(query, options); } - findByTypeAndNameContainingUsername(type, name, username, options) { + findByTypeInIdsAndNameContaining(type, ids, name, options) { + const nameRegex = new RegExp(s.trim(s.escapeRegExp(name)), 'i'); + const query = { - name, - t: type, - usernames: username + _id: { + $in: ids + }, + name: nameRegex, + t: type }; return this.find(query, options); @@ -431,98 +341,6 @@ class ModelRooms extends RocketChat.models._Base { return this.update(query, update); } - addUsernameById(_id, username, muted) { - const query = {_id}; - - const update = { - $addToSet: { - usernames: username - } - }; - - if (muted) { - update.$addToSet.muted = username; - } - - return this.update(query, update); - } - - addUsernamesById(_id, usernames) { - const query = {_id}; - - const update = { - $addToSet: { - usernames: { - $each: usernames - } - } - }; - - return this.update(query, update); - } - - addUsernameByName(name, username) { - const query = {name}; - - const update = { - $addToSet: { - usernames: username - } - }; - - return this.update(query, update); - } - - removeUsernameById(_id, username) { - const query = {_id}; - - const update = { - $pull: { - usernames: username - } - }; - - return this.update(query, update); - } - - removeUsernamesById(_id, usernames) { - const query = {_id}; - - const update = { - $pull: { - usernames: { - $in: usernames - } - } - }; - - return this.update(query, update); - } - - removeUsernameFromAll(username) { - const query = {usernames: username}; - - const update = { - $pull: { - usernames: username - } - }; - - return this.update(query, update, { multi: true }); - } - - removeUsernameByName(name, username) { - const query = {name}; - - const update = { - $pull: { - usernames: username - } - }; - - return this.update(query, update); - } - setNameById(_id, name, fname) { const query = {_id}; @@ -581,6 +399,34 @@ class ModelRooms extends RocketChat.models._Base { 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}); + } + setLastMessageById(_id, lastMessage) { const query = {_id}; @@ -801,6 +647,7 @@ class ModelRooms extends RocketChat.models._Base { t: type, usernames, msgs: 0, + usersCount: 0, u: { _id: user._id, username: user.username @@ -820,7 +667,8 @@ class ModelRooms extends RocketChat.models._Base { t: type, name, usernames: [], - msgs: 0 + msgs: 0, + usersCount: 0 }; _.extend(room, extraData); @@ -844,9 +692,9 @@ class ModelRooms extends RocketChat.models._Base { return this.remove(query); } - removeByTypeContainingUsername(type, username) { + removeDirectRoomContainingUsername(username) { const query = { - t: type, + t: 'd', usernames: username }; diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 12d40fedee78..d3bfa893c3dc 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -3,6 +3,7 @@ class ModelSubscriptions extends RocketChat.models._Base { super(...arguments); this.tryEnsureIndex({ 'rid': 1, 'u._id': 1 }, { unique: 1 }); + this.tryEnsureIndex({ 'rid': 1, 'u.username': 1 }); this.tryEnsureIndex({ 'rid': 1, 'alert': 1, 'u._id': 1 }); this.tryEnsureIndex({ 'rid': 1, 'roles': 1 }); this.tryEnsureIndex({ 'u._id': 1, 'name': 1, 't': 1 }); @@ -20,20 +21,11 @@ class ModelSubscriptions extends RocketChat.models._Base { this.tryEnsureIndex({ 'autoTranslate': 1 }, { sparse: 1 }); this.tryEnsureIndex({ 'autoTranslateLanguage': 1 }, { sparse: 1 }); this.tryEnsureIndex({ 'userHighlights.0': 1 }, { sparse: 1 }); - - this.cache.ensureIndex('rid', 'array'); - this.cache.ensureIndex('u._id', 'array'); - this.cache.ensureIndex('name', 'array'); - this.cache.ensureIndex(['rid', 'u._id'], 'unique'); - this.cache.ensureIndex(['name', 'u._id'], 'unique'); } // FIND ONE findOneByRoomIdAndUserId(roomId, userId, options) { - if (this.useCache) { - return this.cache.findByIndex('rid,u._id', [roomId, userId], options).fetch(); - } const query = { rid: roomId, 'u._id': userId @@ -42,10 +34,16 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.findOne(query, options); } + findOneByRoomIdAndUsername(roomId, username, options) { + const query = { + rid: roomId, + 'u.username': username + }; + + return this.findOne(query, options); + } + findOneByRoomNameAndUserId(roomName, userId) { - if (this.useCache) { - return this.cache.findByIndex('name,u._id', [roomName, userId]).fetch(); - } const query = { name: roomName, 'u._id': userId @@ -56,16 +54,32 @@ class ModelSubscriptions extends RocketChat.models._Base { // FIND findByUserId(userId, options) { - if (this.useCache) { - return this.cache.findByIndex('u._id', userId, options); - } - const query = { 'u._id': userId }; 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); + } + findByUserIdUpdatedAfter(userId, updatedAt, options) { const query = { 'u._id': userId, @@ -106,21 +120,7 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.find(query, options); } - findByTypeNameAndUserId(type, name, userId, options) { - const query = { - t: type, - name, - 'u._id': userId - }; - - return this.find(query, options); - } - findByRoomId(roomId, options) { - if (this.useCache) { - return this.cache.findByIndex('rid', roomId, options); - } - const query = { rid: roomId }; @@ -181,6 +181,18 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.find(query); } + findByRoomIdWhenUserIdExists(rid, options) { + const query = { rid, 'u._id': { $exists: 1 } }; + + return this.find(query, options); + } + + findByRoomIdWhenUsernameExists(rid, options) { + const query = { rid, 'u.username': { $exists: 1 } }; + + return this.find(query, options); + } + findUnreadByUserId(userId) { const query = { 'u._id': userId, @@ -745,6 +757,21 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + updateDirectFNameByName(name, fname) { + const query = { + t: 'd', + name + }; + + const update = { + $set: { + fname + } + }; + + return this.update(query, update, { multi: true }); + } + // INSERT createWithRoomAndUser(room, user, extraData) { const subscription = { @@ -768,23 +795,43 @@ class ModelSubscriptions extends RocketChat.models._Base { ...extraData }; - return this.insert(subscription); + const result = this.insert(subscription); + + RocketChat.models.Rooms.incUsersCountById(room._id); + + return result; } // REMOVE removeByUserId(userId) { - const query = - { 'u._id': userId }; + const query = { + 'u._id': userId + }; + + const roomIds = this.findByUserId(userId).map(s => s.rid); + + const result = this.remove(query); + + if (Match.test(result, Number) && result > 0) { + RocketChat.models.Rooms.incUsersCountByIds(roomIds, -1); + } - return this.remove(query); + return result; } removeByRoomId(roomId) { - const query = - { rid: roomId }; + const query = { + rid: roomId + }; + + const result = this.remove(query); - return this.remove(query); + if (Match.test(result, Number) && result > 0) { + RocketChat.models.Rooms.incUsersCountById(roomId, - result); + } + + return result; } removeByRoomIdAndUserId(roomId, userId) { @@ -793,7 +840,13 @@ class ModelSubscriptions extends RocketChat.models._Base { 'u._id': userId }; - return this.remove(query); + const result = this.remove(query); + + if (Match.test(result, Number) && result > 0) { + RocketChat.models.Rooms.incUsersCountById(roomId, - result); + } + + return result; } } diff --git a/packages/rocketchat-lib/server/models/Users.js b/packages/rocketchat-lib/server/models/Users.js index 5424fb97982d..36ca22f57c1a 100644 --- a/packages/rocketchat-lib/server/models/Users.js +++ b/packages/rocketchat-lib/server/models/Users.js @@ -12,8 +12,6 @@ class ModelUsers extends RocketChat.models._Base { this.tryEnsureIndex({ 'active': 1 }, { sparse: 1 }); this.tryEnsureIndex({ 'statusConnection': 1 }, { sparse: 1 }); this.tryEnsureIndex({ 'type': 1 }); - - this.cache.ensureIndex('username', 'unique'); } findOneByImportId(_id, options) { @@ -31,13 +29,13 @@ class ModelUsers extends RocketChat.models._Base { } findOneByEmailAddress(emailAddress, options) { - const query = {'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i')}; + const query = {'emails.address': new RegExp(`^${ s.escapeRegExp(emailAddress) }$`, 'i')}; return this.findOne(query, options); } findOneAdmin(admin, options) { - const query = {admin}; + const query = {admin}; return this.findOne(query, options); } @@ -52,18 +50,23 @@ class ModelUsers extends RocketChat.models._Base { } findOneById(userId, options) { - const query = {_id: userId}; + const query = { _id: userId }; return this.findOne(query, options); } // FIND findById(userId) { - const query = {_id: userId}; + const query = { _id: userId }; return this.find(query); } + findByIds(users, options) { + const query = { _id: { $in: users } }; + return this.find(query, options); + } + findUsersNotOffline(options) { const query = { username: { @@ -79,33 +82,7 @@ class ModelUsers extends RocketChat.models._Base { findByUsername(username, options) { - const query = {username}; - - return this.find(query, options); - } - - findUsersByUsernamesWithHighlights(usernames, options) { - if (this.useCache) { - const result = { - fetch() { - return RocketChat.models.Users.getDynamicView('highlights').data().filter(record => usernames.indexOf(record.username) > -1); - }, - count() { - return result.fetch().length; - }, - forEach(fn) { - return result.fetch().forEach(fn); - } - }; - return result; - } - - const query = { - username: { $in: usernames }, - 'settings.preferences.highlights.0': { - $exists: true - } - }; + const query = { username }; return this.find(query, options); } @@ -206,13 +183,13 @@ class ModelUsers extends RocketChat.models._Base { } findLDAPUsers(options) { - const query = {ldap: true}; + const query = {ldap: true}; return this.find(query, options); } findCrowdUsers(options) { - const query = {crowd: true}; + const query = {crowd: true}; return this.find(query, options); } @@ -245,11 +222,40 @@ class ModelUsers extends RocketChat.models._Base { return this.find(query, options); } + findUsersWithUsernameByIds(ids, options) { + const query = { + _id: { + $in: ids + }, + username: { + $exists: 1 + } + }; + + return this.find(query, options); + } + + findUsersWithUsernameByIdsNotOffline(ids, options) { + const query = { + _id: { + $in: ids + }, + username: { + $exists: 1 + }, + status: { + $in: ['online', 'away', 'busy'] + } + }; + + return this.find(query, options); + } + // UPDATE addImportIds(_id, importIds) { importIds = [].concat(importIds); - const query = {_id}; + const query = {_id}; const update = { $addToSet: { diff --git a/packages/rocketchat-lib/server/models/_Base.js b/packages/rocketchat-lib/server/models/_Base.js index 6431305c01df..e124e4ca387d 100644 --- a/packages/rocketchat-lib/server/models/_Base.js +++ b/packages/rocketchat-lib/server/models/_Base.js @@ -1,42 +1,22 @@ import ModelsBaseDb from './_BaseDb'; -import ModelsBaseCache from './_BaseCache'; - -RocketChat.models._CacheControl = new Meteor.EnvironmentVariable(); +import objectPath from 'object-path'; +import _ from 'underscore'; class ModelsBase { - constructor(nameOrModel, useCache) { + constructor(nameOrModel) { this._db = new ModelsBaseDb(nameOrModel, this); this.model = this._db.model; this.collectionName = this._db.collectionName; this.name = this._db.name; - this._useCache = useCache === true; - - this.cache = new ModelsBaseCache(this); - // TODO_CACHE: remove - this.on = this.cache.on.bind(this.cache); - this.emit = this.cache.emit.bind(this.cache); - this.getDynamicView = this.cache.getDynamicView.bind(this.cache); - this.processQueryOptionsOnResult = this.cache.processQueryOptionsOnResult.bind(this.cache); - // END_TODO_CACHE + this.on = this._db.on.bind(this._db); + this.emit = this._db.emit.bind(this._db); this.db = this; - - if (this._useCache) { - this.db = new this.constructor(this.model, false); - } - } - - get useCache() { - if (RocketChat.models._CacheControl.get() === false) { - return false; - } - - return this._useCache; } get origin() { - return this.useCache === true ? 'cache' : '_db'; + return '_db'; } arrayToCursor(data) { @@ -139,10 +119,121 @@ class ModelsBase { return this._db.trashFind(...arguments); } + trashFindOneById(/*_id, options*/) { + return this._db.trashFindOneById(...arguments); + } + trashFindDeletedAfter(/*deletedAt, query, options*/) { return this._db.trashFindDeletedAfter(...arguments); } + processQueryOptionsOnResult(result, options={}) { + if (result === undefined || result === null) { + return undefined; + } + + if (Array.isArray(result)) { + if (options.sort) { + result = result.sort((a, b) => { + let r = 0; + for (const field in options.sort) { + if (options.sort.hasOwnProperty(field)) { + const direction = options.sort[field]; + let valueA; + let valueB; + if (field.indexOf('.') > -1) { + valueA = objectPath.get(a, field); + valueB = objectPath.get(b, field); + } else { + valueA = a[field]; + valueB = b[field]; + } + if (valueA > valueB) { + r = direction; + break; + } + if (valueA < valueB) { + r = -direction; + break; + } + } + } + return r; + }); + } + + if (typeof options.skip === 'number') { + result.splice(0, options.skip); + } + + if (typeof options.limit === 'number' && options.limit !== 0) { + result.splice(options.limit); + } + } + + if (!options.fields) { + options.fields = {}; + } + + const fieldsToRemove = []; + const fieldsToGet = []; + + for (const field in options.fields) { + if (options.fields.hasOwnProperty(field)) { + if (options.fields[field] === 0) { + fieldsToRemove.push(field); + } else if (options.fields[field] === 1) { + fieldsToGet.push(field); + } + } + } + + if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) { + console.warn('Can\'t mix remove and get fields'); + fieldsToRemove.splice(0, fieldsToRemove.length); + } + + if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) { + fieldsToGet.push('_id'); + } + + const pickFields = (obj, fields) => { + const picked = {}; + fields.forEach((field) => { + if (field.indexOf('.') !== -1) { + objectPath.set(picked, field, objectPath.get(obj, field)); + } else { + picked[field] = obj[field]; + } + }); + return picked; + }; + + if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) { + if (Array.isArray(result)) { + result = result.map((record) => { + if (fieldsToRemove.length > 0) { + return _.omit(record, ...fieldsToRemove); + } + + if (fieldsToGet.length > 0) { + return pickFields(record, fieldsToGet); + } + }); + } else { + if (fieldsToRemove.length > 0) { + return _.omit(result, ...fieldsToRemove); + } + + if (fieldsToGet.length > 0) { + return pickFields(result, fieldsToGet); + } + } + } + + return result; + } + // dinamicTrashFindAfter(method, deletedAt, ...args) { // const scope = { // find: (query={}) => { diff --git a/packages/rocketchat-lib/server/models/_BaseCache.js b/packages/rocketchat-lib/server/models/_BaseCache.js deleted file mode 100644 index 1e01cabb8661..000000000000 --- a/packages/rocketchat-lib/server/models/_BaseCache.js +++ /dev/null @@ -1,950 +0,0 @@ -/* eslint new-cap: 0 */ -import _ from 'underscore'; -import loki from 'lokijs'; -import {EventEmitter} from 'events'; -import objectPath from 'object-path'; - -const logger = new Logger('BaseCache'); - -const lokiEq = loki.LokiOps.$eq; -const lokiNe = loki.LokiOps.$ne; - -loki.LokiOps.$eq = function(a, b) { - if (Array.isArray(a)) { - return a.indexOf(b) !== -1; - } - return lokiEq(a, b); -}; - -loki.LokiOps.$ne = function(a, b) { - if (Array.isArray(a)) { - return a.indexOf(b) === -1; - } - return lokiNe(a, b); -}; - -const lokiIn = loki.LokiOps.$in; -loki.LokiOps.$in = function(a, b) { - if (Array.isArray(a)) { - return a.some(subA => lokiIn(subA, b)); - } - return lokiIn(a, b); -}; - -loki.LokiOps.$nin = function(a, b) { - return !loki.LokiOps.$in(a, b); -}; - -loki.LokiOps.$all = function(a, b) { - return b.every(subB => a.includes(subB)); -}; - -loki.LokiOps.$exists = function(a, b) { - if (b) { - return loki.LokiOps.$ne(a, undefined); - } - - return loki.LokiOps.$eq(a, undefined); -}; - -loki.LokiOps.$elemMatch = function(a, b) { - return _.findWhere(a, b); -}; - -const ignore = [ - 'emit', - 'load', - 'on', - 'addToAllIndexes' -]; - -function traceMethodCalls(target) { - target._stats = {}; - - for (const property in target) { - if (typeof target[property] === 'function' && ignore.indexOf(property) === -1) { - target._stats[property] = { - calls: 0, - time: 0, - avg: 0 - }; - const origMethod = target[property]; - target[property] = function(...args) { - - if (target.loaded !== true) { - return origMethod.apply(target, args); - } - - const startTime = RocketChat.statsTracker.now(); - const result = origMethod.apply(target, args); - const time = Math.round(RocketChat.statsTracker.now() - startTime) / 1000; - target._stats[property].time += time; - target._stats[property].calls++; - target._stats[property].avg = target._stats[property].time / target._stats[property].calls; - - return result; - }; - } - } - - setInterval(function() { - for (const property in target._stats) { - if (target._stats.hasOwnProperty(property) && target._stats[property].time > 0) { - const tags = [`property:${ property }`, `collection:${ target.collectionName }`]; - RocketChat.statsTracker.timing('cache.methods.time', target._stats[property].avg, tags); - RocketChat.statsTracker.increment('cache.methods.totalTime', target._stats[property].time, tags); - RocketChat.statsTracker.increment('cache.methods.count', target._stats[property].calls, tags); - target._stats[property].avg = 0; - target._stats[property].time = 0; - target._stats[property].calls = 0; - } - } - }, 10000); - - target._getStatsAvg = function() { - const stats = []; - for (const property in target._stats) { - if (target._stats.hasOwnProperty(property)) { - stats.push([Math.round(target._stats[property].avg*100)/100, property]); - } - } - return _.sortBy(stats, function(record) { - return record[0]; - }); - }; -} - -class Adapter { - loadDatabase(/*dbname, callback*/) {} - saveDatabase(/*dbname, dbstring, callback*/) {} - deleteDatabase(/*dbname, callback*/) {} -} - -const db = new loki('rocket.chat.json', {adapter: Adapter}); - -class ModelsBaseCache extends EventEmitter { - constructor(model) { - super(); - - traceMethodCalls(this); - - this.indexes = {}; - this.ignoreUpdatedFields = ['_updatedAt']; - - this.query = {}; - this.options = {}; - - this.ensureIndex('_id', 'unique'); - - this.joins = {}; - - this.on('inserted', (...args) => { this.emit('changed', 'inserted', ...args); }); - this.on('removed', (...args) => { this.emit('changed', 'removed', ...args); }); - this.on('updated', (...args) => { this.emit('changed', 'updated', ...args); }); - - this.on('beforeinsert', (...args) => { this.emit('beforechange', 'inserted', ...args); }); - this.on('beforeremove', (...args) => { this.emit('beforechange', 'removed', ...args); }); - this.on('beforeupdate', (...args) => { this.emit('beforechange', 'updated', ...args); }); - - this.on('inserted', (...args) => { this.emit('sync', 'inserted', ...args); }); - this.on('updated', (...args) => { this.emit('sync', 'updated', ...args); }); - this.on('beforeremove', (...args) => { this.emit('sync', 'removed', ...args); }); - - this.db = db; - - this.model = model; - - this.collectionName = this.model._db.collectionName; - this.collection = this.db.addCollection(this.collectionName); - } - - hasOne(join, {field, link}) { - this.join({join, field, link, multi: false}); - } - - hasMany(join, {field, link}) { - this.join({join, field, link, multi: true}); - } - - join({join, field, link, multi}) { - if (!RocketChat.models[join]) { - console.log(`Invalid cache model ${ join }`); - return; - } - - RocketChat.models[join].cache.on('inserted', (record) => { - this.processRemoteJoinInserted({join, field, link, multi, record}); - }); - - RocketChat.models[join].cache.on('beforeupdate', (record, diff) => { - if (diff[link.remote]) { - this.processRemoteJoinRemoved({join, field, link, multi, record}); - } - }); - - RocketChat.models[join].cache.on('updated', (record, diff) => { - if (diff[link.remote]) { - this.processRemoteJoinInserted({join, field, link, multi, record}); - } - }); - - RocketChat.models[join].cache.on('removed', (record) => { - this.processRemoteJoinRemoved({join, field, link, multi, record}); - }); - - this.on('inserted', (localRecord) => { - this.processLocalJoinInserted({join, field, link, multi, localRecord}); - }); - - this.on('beforeupdate', (localRecord, diff) => { - if (diff[link.local]) { - if (multi === true) { - localRecord[field] = []; - } else { - localRecord[field] = undefined; - } - } - }); - - this.on('updated', (localRecord, diff) => { - if (diff[link.local]) { - this.processLocalJoinInserted({join, field, link, multi, localRecord}); - } - }); - } - - processRemoteJoinInserted({field, link, multi, record}) { - let localRecords = this._findByIndex(link.local, objectPath.get(record, link.remote)); - - if (!localRecords) { - return; - } - - if (!Array.isArray(localRecords)) { - localRecords = [localRecords]; - } - - for (let i = 0; i < localRecords.length; i++) { - const localRecord = localRecords[i]; - if (multi === true && !localRecord[field]) { - localRecord[field] = []; - } - - if (typeof link.where === 'function' && link.where(localRecord, record) === false) { - continue; - } - - let mutableRecord = record; - - if (typeof link.transform === 'function') { - mutableRecord = link.transform(localRecord, mutableRecord); - } - - if (multi === true) { - localRecord[field].push(mutableRecord); - } else { - localRecord[field] = mutableRecord; - } - - this.emit(`join:${ field }:inserted`, localRecord, mutableRecord); - this.emit(`join:${ field }:changed`, 'inserted', localRecord, mutableRecord); - } - } - - processLocalJoinInserted({join, field, link, multi, localRecord}) { - let records = RocketChat.models[join].cache._findByIndex(link.remote, objectPath.get(localRecord, link.local)); - - if (!Array.isArray(records)) { - records = [records]; - } - - for (let i = 0; i < records.length; i++) { - let record = records[i]; - - if (typeof link.where === 'function' && link.where(localRecord, record) === false) { - continue; - } - - if (typeof link.transform === 'function') { - record = link.transform(localRecord, record); - } - - if (multi === true) { - localRecord[field].push(record); - } else { - localRecord[field] = record; - } - - this.emit(`join:${ field }:inserted`, localRecord, record); - this.emit(`join:${ field }:changed`, 'inserted', localRecord, record); - } - } - - processRemoteJoinRemoved({field, link, multi, record}) { - let localRecords = this._findByIndex(link.local, objectPath.get(record, link.remote)); - - if (!localRecords) { - return; - } - - if (!Array.isArray(localRecords)) { - localRecords = [localRecords]; - } - - for (let i = 0; i < localRecords.length; i++) { - const localRecord = localRecords[i]; - - if (multi === true) { - if (Array.isArray(localRecord[field])) { - if (typeof link.remove === 'function') { - link.remove(localRecord[field], record); - } else if (localRecord[field].indexOf(record) > -1) { - localRecord[field].splice(localRecord[field].indexOf(record), 1); - } - } - } else { - localRecord[field] = undefined; - } - - this.emit(`join:${ field }:removed`, localRecord, record); - this.emit(`join:${ field }:changed`, 'removed', localRecord, record); - } - } - - ensureIndex(fields, type='array') { - if (!Array.isArray(fields)) { - fields = [fields]; - } - - this.indexes[fields.join(',')] = { - type, - fields, - data: {} - }; - } - - addToAllIndexes(record) { - for (const indexName in this.indexes) { - if (this.indexes.hasOwnProperty(indexName)) { - this.addToIndex(indexName, record); - } - } - } - - addToIndex(indexName, record) { - const index = this.indexes[indexName]; - if (!index) { - console.error(`Index not defined ${ indexName }`); - return; - } - - const keys = []; - for (const field of index.fields) { - keys.push(objectPath.get(record, field)); - } - const key = keys.join('|'); - - if (index.type === 'unique') { - index.data[key] = record; - return; - } - - if (index.type === 'array') { - if (!index.data[key]) { - index.data[key] = []; - } - index.data[key].push(record); - return; - } - } - - removeFromAllIndexes(record) { - for (const indexName in this.indexes) { - if (this.indexes.hasOwnProperty(indexName)) { - this.removeFromIndex(indexName, record); - } - } - } - - removeFromIndex(indexName, record) { - const index = this.indexes[indexName]; - if (!this.indexes[indexName]) { - console.error(`Index not defined ${ indexName }`); - return; - } - - if (!index.data) { - return; - } - - let key = []; - for (const field of index.fields) { - key.push(objectPath.get(record, field)); - } - key = key.join('|'); - - if (index.type === 'unique') { - index.data[key] = undefined; - return; - } - - if (index.type === 'array') { - if (!index.data[key]) { - return; - } - const i = index.data[key].indexOf(record); - if (i > -1) { - index.data[key].splice(i, 1); - } - return; - } - } - - _findByIndex(index, keys) { - const key = [].concat(keys).join('|'); - if (!this.indexes[index]) { - return; - } - - if (this.indexes[index].data) { - const result = this.indexes[index].data[key]; - if (result) { - return result; - } - } - - if (this.indexes[index].type === 'array') { - return []; - } - } - - findByIndex(index, keys, options={}) { - return { - fetch: () => { - return this.processQueryOptionsOnResult(this._findByIndex(index, keys), options); - }, - - count: () => { - const records = this.findByIndex(index, keys, options).fetch(); - if (Array.isArray(records)) { - return records.length; - } - return !records ? 0 : 1; - }, - - forEach: (fn) => { - const records = this.findByIndex(index, keys, options).fetch(); - if (Array.isArray(records)) { - return records.forEach(fn); - } - if (records) { - return fn(records); - } - } - }; - } - - load() { - if (this.model._useCache === false) { - return; - } - - console.log('Will load cache for', this.collectionName); - this.emit('beforeload'); - this.loaded = false; - const time = RocketChat.statsTracker.now(); - const data = this.model.db.find(this.query, this.options).fetch(); - for (let i=0; i < data.length; i++) { - this.insert(data[i]); - } - console.log(String(data.length), 'records load from', this.collectionName); - RocketChat.statsTracker.timing('cache.load', RocketChat.statsTracker.now() - time, [`collection:${ this.collectionName }`]); - - this.startSync(); - this.loaded = true; - this.emit('afterload'); - } - - startSync() { - if (this.model._useCache === false) { - return; - } - - this.model._db.on('change', ({action, id, data/*, oplog*/}) => { - switch (action) { - case 'insert': - data._id = id; - this.insert(data); - break; - - case 'remove': - this.removeById(id); - break; - - case 'update:record': - this.updateDiffById(id, data); - break; - - case 'update:diff': - this.updateDiffById(id, data); - break; - - case 'update:query': - this.update(data.query, data.update, data.options); - break; - } - }); - } - - processQueryOptionsOnResult(result, options={}) { - if (result === undefined || result === null) { - return undefined; - } - - if (Array.isArray(result)) { - if (options.sort) { - result = result.sort((a, b) => { - let r = 0; - for (const field in options.sort) { - if (options.sort.hasOwnProperty(field)) { - const direction = options.sort[field]; - let valueA; - let valueB; - if (field.indexOf('.') > -1) { - valueA = objectPath.get(a, field); - valueB = objectPath.get(b, field); - } else { - valueA = a[field]; - valueB = b[field]; - } - if (valueA > valueB) { - r = direction; - break; - } - if (valueA < valueB) { - r = -direction; - break; - } - } - } - return r; - }); - } - - if (typeof options.skip === 'number') { - result.splice(0, options.skip); - } - - if (typeof options.limit === 'number' && options.limit !== 0) { - result.splice(options.limit); - } - } - - if (!options.fields) { - options.fields = {}; - } - - const fieldsToRemove = []; - const fieldsToGet = []; - - for (const field in options.fields) { - if (options.fields.hasOwnProperty(field)) { - if (options.fields[field] === 0) { - fieldsToRemove.push(field); - } else if (options.fields[field] === 1) { - fieldsToGet.push(field); - } - } - } - - if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) { - console.warn('Can\'t mix remove and get fields'); - fieldsToRemove.splice(0, fieldsToRemove.length); - } - - if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) { - fieldsToGet.push('_id'); - } - - const pickFields = (obj, fields) => { - const picked = {}; - fields.forEach((field) => { - if (field.indexOf('.') !== -1) { - objectPath.set(picked, field, objectPath.get(obj, field)); - } else { - picked[field] = obj[field]; - } - }); - return picked; - }; - - if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) { - if (Array.isArray(result)) { - result = result.map((record) => { - if (fieldsToRemove.length > 0) { - return _.omit(record, ...fieldsToRemove); - } - - if (fieldsToGet.length > 0) { - return pickFields(record, fieldsToGet); - } - }); - } else { - if (fieldsToRemove.length > 0) { - return _.omit(result, ...fieldsToRemove); - } - - if (fieldsToGet.length > 0) { - return pickFields(result, fieldsToGet); - } - } - } - - return result; - } - - processQuery(query, parentField) { - if (!query) { - return query; - } - - if (Match.test(query, String)) { - return { - _id: query - }; - } - - if (Object.keys(query).length > 1 && parentField !== '$elemMatch') { - const and = []; - for (const field in query) { - if (query.hasOwnProperty(field)) { - and.push({ - [field]: query[field] - }); - } - } - query = {$and: and}; - } - - for (const field in query) { - if (query.hasOwnProperty(field)) { - const value = query[field]; - if (value instanceof RegExp && field !== '$regex') { - query[field] = { - $regex: value - }; - } - - if (field === '$and' || field === '$or') { - query[field] = value.map((subValue) => { - return this.processQuery(subValue, field); - }); - } - - if (Match.test(value, Object) && Object.keys(value).length > 0) { - query[field] = this.processQuery(value, field); - } - } - } - - return query; - } - - find(query, options={}) { - return { - fetch: () => { - try { - query = this.processQuery(query); - return this.processQueryOptionsOnResult(this.collection.find(query), options); - } catch (e) { - console.error('Exception on cache find for', this.collectionName); - console.error('Query:', JSON.stringify(query, null, 2)); - console.error('Options:', JSON.stringify(options, null, 2)); - console.error(e.stack); - throw e; - } - }, - - count: () => { - try { - query = this.processQuery(query); - const { limit, skip } = options; - return this.processQueryOptionsOnResult(this.collection.find(query), { limit, skip }).length; - } catch (e) { - console.error('Exception on cache find for', this.collectionName); - console.error('Query:', JSON.stringify(query, null, 2)); - console.error('Options:', JSON.stringify(options, null, 2)); - console.error(e.stack); - throw e; - } - }, - - forEach: (fn) => { - return this.find(query, options).fetch().forEach(fn); - }, - - observe: (obj) => { - logger.debug(this.collectionName, 'Falling back observe to model with query:', query); - return this.model.db.find(...arguments).observe(obj); - }, - - observeChanges: (obj) => { - logger.debug(this.collectionName, 'Falling back observeChanges to model with query:', query); - return this.model.db.find(...arguments).observeChanges(obj); - }, - - _publishCursor: (cursor, sub, collection) => { - logger.debug(this.collectionName, 'Falling back _publishCursor to model with query:', query); - return this.model.db.find(...arguments)._publishCursor(cursor, sub, collection); - } - }; - } - - findOne(query, options) { - try { - query = this.processQuery(query); - return this.processQueryOptionsOnResult(this.collection.findOne(query), options); - } catch (e) { - console.error('Exception on cache findOne for', this.collectionName); - console.error('Query:', JSON.stringify(query, null, 2)); - console.error('Options:', JSON.stringify(options, null, 2)); - console.error(e.stack); - throw e; - } - } - - findOneById(_id, options) { - return this.findByIndex('_id', _id, options).fetch(); - } - - findOneByIds(ids, options) { - const query = this.processQuery({ _id: { $in: ids }}); - return this.processQueryOptionsOnResult(this.collection.findOne(query), options); - } - - findWhere(query, options) { - query = this.processQuery(query); - return this.processQueryOptionsOnResult(this.collection.findWhere(query), options); - } - - addDynamicView() { - return this.collection.addDynamicView(...arguments); - } - - getDynamicView() { - return this.collection.getDynamicView(...arguments); - } - - insert(record) { - if (Array.isArray(record)) { - for (const item of record) { - this.insert(item); - } - } else { - // TODO remove - ignore updates in room.usernames - if (this.collectionName === 'rocketchat_room' && record.usernames) { - delete record.usernames; - } - this.emit('beforeinsert', record); - this.addToAllIndexes(record); - this.collection.insert(record); - this.emit('inserted', record); - } - } - - updateDiffById(id, diff) { - // TODO remove - ignore updates in room.usernames - if (this.collectionName === 'rocketchat_room' && diff.usernames) { - delete diff.usernames; - } - - const record = this._findByIndex('_id', id); - if (!record) { - console.error('Cache.updateDiffById: No record', this.collectionName, id, diff); - return; - } - this.removeFromAllIndexes(record); - - const updatedFields = _.without(Object.keys(diff), ...this.ignoreUpdatedFields); - - if (updatedFields.length > 0) { - this.emit('beforeupdate', record, diff); - } - - for (const key in diff) { - if (diff.hasOwnProperty(key)) { - objectPath.set(record, key, diff[key]); - } - } - - this.collection.update(record); - this.addToAllIndexes(record); - - if (updatedFields.length > 0) { - this.emit('updated', record, diff); - } - } - - updateRecord(record, update) { - // TODO remove - ignore updates in room.usernames - if (this.collectionName === 'rocketchat_room' && (record.usernames || (record.$set && record.$set.usernames))) { - delete record.usernames; - if (record.$set && record.$set.usernames) { - delete record.$set.usernames; - } - } - - this.removeFromAllIndexes(record); - - const topLevelFields = Object.keys(update).map(field => field.split('.')[0]); - const updatedFields = _.without(topLevelFields, ...this.ignoreUpdatedFields); - - if (updatedFields.length > 0) { - this.emit('beforeupdate', record, record); - } - - if (update.$set) { - _.each(update.$set, (value, field) => { - objectPath.set(record, field, value); - }); - } - - if (update.$unset) { - _.each(update.$unset, (value, field) => { - objectPath.del(record, field); - }); - } - - if (update.$min) { - _.each(update.$min, (value, field) => { - const curValue = objectPath.get(record, field); - if (curValue === undefined || value < curValue) { - objectPath.set(record, field, value); - } - }); - } - - if (update.$max) { - _.each(update.$max, (value, field) => { - const curValue = objectPath.get(record, field); - if (curValue === undefined || value > curValue) { - objectPath.set(record, field, value); - } - }); - } - - if (update.$inc) { - _.each(update.$inc, (value, field) => { - let curValue = objectPath.get(record, field); - if (curValue === undefined) { - curValue = value; - } else { - curValue += value; - } - objectPath.set(record, field, curValue); - }); - } - - if (update.$mul) { - _.each(update.$mul, (value, field) => { - let curValue = objectPath.get(record, field); - if (curValue === undefined) { - curValue = 0; - } else { - curValue *= value; - } - objectPath.set(record, field, curValue); - }); - } - - if (update.$rename) { - _.each(update.$rename, (value, field) => { - const curValue = objectPath.get(record, field); - if (curValue !== undefined) { - objectPath.set(record, value, curValue); - objectPath.del(record, field); - } - }); - } - - if (update.$pullAll) { - _.each(update.$pullAll, (value, field) => { - let curValue = objectPath.get(record, field); - if (Array.isArray(curValue)) { - curValue = _.difference(curValue, value); - objectPath.set(record, field, curValue); - } - }); - } - - if (update.$pop) { - _.each(update.$pop, (value, field) => { - const curValue = objectPath.get(record, field); - if (Array.isArray(curValue)) { - if (value === -1) { - curValue.shift(); - } else { - curValue.pop(); - } - objectPath.set(record, field, curValue); - } - }); - } - - if (update.$addToSet) { - _.each(update.$addToSet, (value, field) => { - let curValue = objectPath.get(record, field); - if (curValue === undefined) { - curValue = []; - } - if (Array.isArray(curValue)) { - const length = curValue.length; - - if (value && value.$each && Array.isArray(value.$each)) { - for (const valueItem of value.$each) { - if (curValue.indexOf(valueItem) === -1) { - curValue.push(valueItem); - } - } - } else if (curValue.indexOf(value) === -1) { - curValue.push(value); - } - - if (curValue.length > length) { - objectPath.set(record, field, curValue); - } - } - }); - } - - this.collection.update(record); - this.addToAllIndexes(record); - - if (updatedFields.length > 0) { - this.emit('updated', record, record); - } - } - - update(query, update, options = {}) { - let records = options.multi ? this.find(query).fetch() : this.findOne(query) || []; - if (!Array.isArray(records)) { - records = [records]; - } - - for (const record of records) { - this.updateRecord(record, update); - } - } - - removeById(id) { - const record = this._findByIndex('_id', id); - if (record) { - this.emit('beforeremove', record); - this.collection.removeWhere({_id: id}); - this.removeFromAllIndexes(record); - this.emit('removed', record); - } - } -} - -export default ModelsBaseCache; diff --git a/packages/rocketchat-lib/server/models/_BaseDb.js b/packages/rocketchat-lib/server/models/_BaseDb.js index b93f3e917701..a39f0827ca7d 100644 --- a/packages/rocketchat-lib/server/models/_BaseDb.js +++ b/packages/rocketchat-lib/server/models/_BaseDb.js @@ -36,9 +36,11 @@ class ModelsBaseDb extends EventEmitter { this.wrapModel(); + let alreadyListeningToOplog = false; // When someone start listening for changes we start oplog if available - this.once('newListener', (event/*, listener*/) => { - if (event === 'change') { + this.on('newListener', (event/*, listener*/) => { + if (event === 'change' && alreadyListeningToOplog === false) { + alreadyListeningToOplog = true; if (isOplogEnabled) { const query = { collection: this.collectionName @@ -98,65 +100,32 @@ class ModelsBaseDb extends EventEmitter { }; } + _doNotMixInclusionAndExclusionFields(options) { + if (options && options.fields) { + const keys = Object.keys(options.fields); + const removeKeys = keys.filter(key => options.fields[key] === 0); + if (keys.length > removeKeys.length) { + removeKeys.forEach(key => delete options.fields[key]); + } + } + } + find() { + this._doNotMixInclusionAndExclusionFields(arguments[1]); return this.model.find(...arguments); } findOne() { + this._doNotMixInclusionAndExclusionFields(arguments[1]); return this.model.findOne(...arguments); } findOneById(_id, options) { - return this.model.findOne({ _id }, options); + return this.findOne({ _id }, options); } findOneByIds(ids, options) { - return this.model.findOne({ _id: { $in: ids }}, options); - } - - defineSyncStrategy(query, modifier, options) { - if (this.baseModel.useCache === false) { - return 'db'; - } - - if (options.upsert === true) { - return 'db'; - } - - // const dbModifiers = [ - // '$currentDate', - // '$bit', - // '$pull', - // '$pushAll', - // '$push', - // '$setOnInsert' - // ]; - - const cacheAllowedModifiers = [ - '$set', - '$unset', - '$min', - '$max', - '$inc', - '$mul', - '$rename', - '$pullAll', - '$pop', - '$addToSet' - ]; - - const notAllowedModifiers = Object.keys(modifier).filter(i => i.startsWith('$') && cacheAllowedModifiers.includes(i) === false); - - if (notAllowedModifiers.length > 0) { - return 'db'; - } - - const placeholderFields = Object.keys(query).filter(item => item.indexOf('$') > -1); - if (placeholderFields.length > 0) { - return 'db'; - } - - return 'cache'; + return this.findOne({ _id: { $in: ids }}, options); } updateHasPositionalOperator(update) { @@ -171,6 +140,7 @@ class ModelsBaseDb extends EventEmitter { if (action.op.op === 'i') { this.emit('change', { action: 'insert', + clientAction: 'inserted', id: action.op.o._id, data: action.op.o, oplog: true @@ -181,7 +151,8 @@ class ModelsBaseDb extends EventEmitter { if (action.op.op === 'u') { if (!action.op.o.$set && !action.op.o.$unset) { this.emit('change', { - action: 'update:record', + action: 'update', + clientAction: 'updated', id: action.id, data: action.op.o, oplog: true @@ -207,9 +178,10 @@ class ModelsBaseDb extends EventEmitter { } this.emit('change', { - action: 'update:diff', + action: 'update', + clientAction: 'updated', id: action.id, - data: diff, + diff, oplog: true }); return; @@ -218,6 +190,7 @@ class ModelsBaseDb extends EventEmitter { if (action.op.op === 'd') { this.emit('change', { action: 'remove', + clientAction: 'removed', id: action.id, oplog: true }); @@ -229,27 +202,28 @@ class ModelsBaseDb extends EventEmitter { this.setUpdatedAt(record); const result = this.originals.insert(...arguments); + + record._id = result; + if (!isOplogEnabled && this.listenerCount('change') > 0) { this.emit('change', { action: 'insert', + clientAction: 'inserted', id: result, data: _.extend({}, record), oplog: false }); } - record._id = result; - return result; } update(query, update, options = {}) { this.setUpdatedAt(update, true, query); - const strategy = this.defineSyncStrategy(query, update, options); let ids = []; - if (!isOplogEnabled && this.listenerCount('change') > 0 && strategy === 'db') { - const findOptions = {fields: {_id: 1}}; + if (!isOplogEnabled && this.listenerCount('change') > 0) { + const findOptions = { fields: { _id: 1 } }; let records = options.multi ? this.find(query, findOptions).fetch() : this.findOne(query, findOptions) || []; if (!Array.isArray(records)) { records = [records]; @@ -265,53 +239,31 @@ class ModelsBaseDb extends EventEmitter { } } + // TODO: CACHE: Can we use findAndModify here when oplog is disabled? const result = this.originals.update(query, update, options); if (!isOplogEnabled && this.listenerCount('change') > 0) { - if (strategy === 'db') { - if (options.upsert === true) { - if (result.insertedId) { - this.emit('change', { - action: 'insert', - id: result.insertedId, - data: this.findOne({_id: result.insertedId}), - oplog: false - }); - return; - } + if (options.upsert === true && result.insertedId) { + this.emit('change', { + action: 'insert', + clientAction: 'inserted', + id: result.insertedId, + oplog: false + }); - query = { - _id: { - $in: ids - } - }; - } + return result; + } - let records = options.multi ? this.find(query).fetch() : this.findOne(query) || []; - if (!Array.isArray(records)) { - records = [records]; - } - for (const record of records) { - this.emit('change', { - action: 'update:record', - id: record._id, - data: record, - oplog: false - }); - } - } else { + for (const id of ids) { this.emit('change', { - action: 'update:query', - id: undefined, - data: { - query, - update, - options - }, + action: 'update', + clientAction: 'updated', + id, oplog: false }); } } + return result; } @@ -342,6 +294,7 @@ class ModelsBaseDb extends EventEmitter { for (const record of records) { this.emit('change', { action: 'remove', + clientAction: 'removed', id: record._id, data: _.extend({}, record), oplog: false @@ -405,6 +358,15 @@ class ModelsBaseDb extends EventEmitter { return trash.find(query, options); } + trashFindOneById(_id, options) { + const query = { + _id, + __collection__: this.name + }; + + return trash.findOne(query, options); + } + trashFindDeletedAfter(deletedAt, query = {}, options) { query.__collection__ = this.name; query._deletedAt = { diff --git a/packages/rocketchat-lib/server/publications/settings.js b/packages/rocketchat-lib/server/publications/settings.js index 5707468422bf..175ccea3cae7 100644 --- a/packages/rocketchat-lib/server/publications/settings.js +++ b/packages/rocketchat-lib/server/publications/settings.js @@ -1,11 +1,8 @@ -import _ from 'underscore'; - Meteor.methods({ 'public-settings/get'(updatedAt) { this.unblock(); - const records = RocketChat.models.Settings.find().fetch().filter(function(record) { - return record.hidden !== true && record['public'] === true; - }); + const records = RocketChat.models.Settings.findNotHiddenPublic().fetch(); + if (updatedAt instanceof Date) { return { update: records.filter(function(record) { @@ -34,9 +31,7 @@ Meteor.methods({ if (!RocketChat.authz.hasPermission(Meteor.userId(), 'view-privileged-setting')) { return []; } - const records = RocketChat.models.Settings.find().fetch().filter(function(record) { - return record.hidden !== true; - }); + const records = RocketChat.models.Settings.findNotHidden().fetch(); if (updatedAt instanceof Date) { return { update: records.filter(function(record) { @@ -58,11 +53,30 @@ Meteor.methods({ } }); -RocketChat.models.Settings.cache.on('changed', function(type, setting) { - if (setting['public'] === true) { - RocketChat.Notifications.notifyAllInThisInstance('public-settings-changed', type, _.pick(setting, '_id', 'value', 'editor', 'properties')); +RocketChat.models.Settings.on('change', ({clientAction, id, data}) => { + switch (clientAction) { + case 'updated': + case 'inserted': + const setting = data || RocketChat.models.Settings.findOneById(id); + const value = { + _id: setting._id, + value: setting.value, + editor: setting.editor, + properties: setting.properties + }; + + if (setting['public'] === true) { + RocketChat.Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); + } else { + RocketChat.Notifications.notifyLoggedInThisInstance('private-settings-changed', clientAction, setting); + } + break; + + case 'removed': + RocketChat.Notifications.notifyLoggedInThisInstance('private-settings-changed', clientAction, { _id: id }); + RocketChat.Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, { _id: id }); + break; } - return RocketChat.Notifications.notifyLoggedInThisInstance('private-settings-changed', type, setting); }); RocketChat.Notifications.streamAll.allowRead('private-settings-changed', function() { diff --git a/packages/rocketchat-lib/server/startup/cache/CacheLoad.js b/packages/rocketchat-lib/server/startup/cache/CacheLoad.js deleted file mode 100644 index 8f555fc454c7..000000000000 --- a/packages/rocketchat-lib/server/startup/cache/CacheLoad.js +++ /dev/null @@ -1,73 +0,0 @@ -RocketChat.models.Rooms.cache.hasMany('Subscriptions', { - field: 'usernames', - link: { - local: '_id', - remote: 'rid', - transform(room, subscription) { - return subscription.u.username; - }, - remove(arr, subscription) { - if (arr.indexOf(subscription.u.username) > -1) { - arr.splice(arr.indexOf(subscription.u.username), 1); - } - } - } -}); - - -RocketChat.models.Subscriptions.cache.hasOne('Rooms', { - field: '_room', - link: { - local: 'rid', - remote: '_id' - } -}); - - -RocketChat.models.Subscriptions.cache.hasOne('Users', { - field: '_user', - link: { - local: 'u._id', - remote: '_id' - } -}); - -RocketChat.models.Subscriptions.cache.hasOne('Users', { - field: 'fname', - link: { - local: 'name', - remote: 'username', - where(subscription/*, user*/) { - return subscription.t === 'd'; - }, - transform(subscription, user) { - if (user == null || subscription == null) { - return undefined; - } - // Prevent client cache for old subscriptions with new names - // Cuz when a user change his name, the subscription's _updateAt - // will not change - if (subscription._updatedAt < user._updatedAt) { - subscription._updatedAt = user._updatedAt; - } - return user.name; - } - } -}); - -RocketChat.models.Users.cache.load(); -RocketChat.models.Rooms.cache.load(); -RocketChat.models.Subscriptions.cache.load(); -RocketChat.models.Settings.cache.load(); - - -RocketChat.models.Users.cache.addDynamicView('highlights').applyFind({ - 'settings.preferences.highlights': {$size: {$gt: 0}} -}); - -RocketChat.models.Subscriptions.cache.addDynamicView('notifications').applyFind({ - $or: [ - {desktopNotifications: {$in: ['all', 'nothing']}}, - {mobilePushNotifications: {$in: ['all', 'nothing']}} - ] -}); diff --git a/packages/rocketchat-livechat/server/lib/Livechat.js b/packages/rocketchat-livechat/server/lib/Livechat.js index c8e19915cb74..3dc0f9b32604 100644 --- a/packages/rocketchat-livechat/server/lib/Livechat.js +++ b/packages/rocketchat-livechat/server/lib/Livechat.js @@ -365,8 +365,6 @@ RocketChat.Livechat = { const servedBy = room.servedBy; if (agent && agent.agentId !== servedBy._id) { - room.usernames = _.without(room.usernames, servedBy.username).concat(agent.username); - RocketChat.models.Rooms.changeAgentByRoomId(room._id, agent); const subscriptionData = { @@ -389,6 +387,7 @@ RocketChat.Livechat = { RocketChat.models.Subscriptions.removeByRoomIdAndUserId(room._id, servedBy._id); RocketChat.models.Subscriptions.insert(subscriptionData); + RocketChat.models.Rooms.incUsersCountById(room._id); RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(room._id, { _id: servedBy._id, username: servedBy.username }); RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(room._id, { _id: agent.agentId, username: agent.username }); diff --git a/packages/rocketchat-livechat/server/lib/QueueMethods.js b/packages/rocketchat-livechat/server/lib/QueueMethods.js index 0523b98e355b..d167f18df62a 100644 --- a/packages/rocketchat-livechat/server/lib/QueueMethods.js +++ b/packages/rocketchat-livechat/server/lib/QueueMethods.js @@ -19,6 +19,7 @@ RocketChat.QueueMethods = { const room = _.extend({ _id: message.rid, msgs: 1, + usersCount: 1, lm: new Date(), fname: (roomInfo && roomInfo.fname) || guest.name || guest.username, // usernames: [agent.username, guest.username], @@ -38,6 +39,7 @@ RocketChat.QueueMethods = { open: true, waitingResponse: true }, roomInfo); + const subscriptionData = { rid: message.rid, fname: guest.name || guest.username, @@ -57,6 +59,7 @@ RocketChat.QueueMethods = { }; RocketChat.models.Rooms.insert(room); + RocketChat.models.Subscriptions.insert(subscriptionData); RocketChat.Livechat.stream.emit(room._id, { @@ -114,9 +117,11 @@ RocketChat.QueueMethods = { }, t: 'l' }; + const room = _.extend({ _id: message.rid, msgs: 1, + usersCount: 0, lm: new Date(), fname: guest.name || guest.username, // usernames: [guest.username], @@ -132,6 +137,7 @@ RocketChat.QueueMethods = { open: true, waitingResponse: true }, roomInfo); + RocketChat.models.LivechatInquiry.insert(inquiry); RocketChat.models.Rooms.insert(room); diff --git a/packages/rocketchat-livechat/server/methods/closeRoom.js b/packages/rocketchat-livechat/server/methods/closeRoom.js index b61350ec9ff9..6013a517a664 100644 --- a/packages/rocketchat-livechat/server/methods/closeRoom.js +++ b/packages/rocketchat-livechat/server/methods/closeRoom.js @@ -1,6 +1,7 @@ Meteor.methods({ 'livechat:closeRoom'(roomId, comment) { - if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'close-livechat-room')) { + const userId = Meteor.userId(); + if (!userId || !RocketChat.authz.hasPermission(userId, 'close-livechat-room')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'livechat:closeRoom' }); } @@ -12,7 +13,8 @@ Meteor.methods({ const user = Meteor.user(); - if ((!room.usernames || room.usernames.indexOf(user.username) === -1) && !RocketChat.authz.hasPermission(Meteor.userId(), 'close-others-livechat-room')) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, user._id, { _id: 1 }); + if (!subscription && !RocketChat.authz.hasPermission(userId, 'close-others-livechat-room')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'livechat:closeRoom' }); } diff --git a/packages/rocketchat-livechat/server/methods/returnAsInquiry.js b/packages/rocketchat-livechat/server/methods/returnAsInquiry.js index 91abafb0444a..059f2687956a 100644 --- a/packages/rocketchat-livechat/server/methods/returnAsInquiry.js +++ b/packages/rocketchat-livechat/server/methods/returnAsInquiry.js @@ -7,11 +7,6 @@ Meteor.methods({ // //delete agent and room subscription RocketChat.models.Subscriptions.removeByRoomId(rid); - // remove user from room - const username = Meteor.user().username; - - RocketChat.models.Rooms.removeUsernameById(rid, username); - // find inquiry corresponding to room const inquiry = RocketChat.models.LivechatInquiry.findOne({rid}); diff --git a/packages/rocketchat-livechat/server/methods/takeInquiry.js b/packages/rocketchat-livechat/server/methods/takeInquiry.js index 8f1eed7c3fc7..418feb9d6680 100644 --- a/packages/rocketchat-livechat/server/methods/takeInquiry.js +++ b/packages/rocketchat-livechat/server/methods/takeInquiry.js @@ -35,7 +35,9 @@ Meteor.methods({ mobilePushNotifications: 'all', emailNotifications: 'all' }; + RocketChat.models.Subscriptions.insert(subscriptionData); + RocketChat.models.Rooms.incUsersCountById(inquiry.rid); // update room const room = RocketChat.models.Rooms.findOneById(inquiry.rid); diff --git a/packages/rocketchat-livechat/server/methods/transfer.js b/packages/rocketchat-livechat/server/methods/transfer.js index e2af06273488..aac6d632a5dd 100644 --- a/packages/rocketchat-livechat/server/methods/transfer.js +++ b/packages/rocketchat-livechat/server/methods/transfer.js @@ -18,9 +18,8 @@ Meteor.methods({ const guest = LivechatVisitors.findOneById(room.v._id); - const user = Meteor.user(); - - if (room.usernames.indexOf(user.username) === -1 && !RocketChat.authz.hasRole(Meteor.userId(), 'livechat-manager')) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, Meteor.userId(), { fields: { _id: 1 } }); + if (!subscription && !RocketChat.authz.hasRole(Meteor.userId(), 'livechat-manager')) { throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'livechat:transfer' }); } diff --git a/packages/rocketchat-livechat/server/publications/visitorHistory.js b/packages/rocketchat-livechat/server/publications/visitorHistory.js index e90e34fc1470..f46711de7347 100644 --- a/packages/rocketchat-livechat/server/publications/visitorHistory.js +++ b/packages/rocketchat-livechat/server/publications/visitorHistory.js @@ -9,9 +9,8 @@ Meteor.publish('livechat:visitorHistory', function({ rid: roomId }) { const room = RocketChat.models.Rooms.findOneById(roomId); - const user = RocketChat.models.Users.findOneById(this.userId); - - if (room.usernames.indexOf(user.username) === -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }); + if (!subscription) { return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:visitorHistory' })); } diff --git a/packages/rocketchat-message-pin/client/actionButton.js b/packages/rocketchat-message-pin/client/actionButton.js index 407904b6dfe2..02c16203be3f 100644 --- a/packages/rocketchat-message-pin/client/actionButton.js +++ b/packages/rocketchat-message-pin/client/actionButton.js @@ -16,7 +16,7 @@ Meteor.startup(function() { }); }, condition(message) { - if (!RocketChat.settings.get('Message_AllowPinning') || message.pinned || !RocketChat.models.Subscriptions.findOne({ rid: message.rid }, {fields: {_id: 1}})) { + if (!RocketChat.settings.get('Message_AllowPinning') || message.pinned || !RocketChat.models.Subscriptions.findOne({ rid: message.rid }, { fields: { _id: 1 } })) { return false; } @@ -41,7 +41,7 @@ Meteor.startup(function() { }); }, condition(message) { - if (!RocketChat.settings.get('Message_AllowPinning') || !message.pinned || !RocketChat.models.Subscriptions.findOne({ rid: message.rid }, {fields: {_id: 1}})) { + if (!RocketChat.settings.get('Message_AllowPinning') || !message.pinned || !RocketChat.models.Subscriptions.findOne({ rid: message.rid }, { fields: { _id: 1 } })) { return false; } @@ -64,7 +64,7 @@ Meteor.startup(function() { return RoomHistoryManager.getSurroundingMessages(message, 50); }, condition(message) { - if (!RocketChat.models.Subscriptions.findOne({ rid: message.rid }, {fields: {_id: 1}})) { + if (!RocketChat.models.Subscriptions.findOne({ rid: message.rid }, { fields: { _id: 1 } })) { return false; } return true; @@ -85,7 +85,7 @@ Meteor.startup(function() { toastr.success(TAPi18n.__('Copied')); }, condition(message) { - if (!RocketChat.models.Subscriptions.findOne({ rid: message.rid }, {fields: {_id: 1}})) { + if (!RocketChat.models.Subscriptions.findOne({ rid: message.rid }, { fields: { _id: 1 } })) { return false; } return true; diff --git a/packages/rocketchat-message-pin/server/pinMessage.js b/packages/rocketchat-message-pin/server/pinMessage.js index 97cd6cdd5b1e..5be88399d572 100644 --- a/packages/rocketchat-message-pin/server/pinMessage.js +++ b/packages/rocketchat-message-pin/server/pinMessage.js @@ -33,8 +33,8 @@ Meteor.methods({ }); } - const room = RocketChat.models.Rooms.findOneById(message.rid); - if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId(), { fields: { _id: 1 } }); + if (!subscription) { return false; } @@ -108,9 +108,8 @@ Meteor.methods({ }); } - const room = RocketChat.models.Rooms.findOneById(message.rid); - - if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId(), { fields: { _id: 1 } }); + if (!subscription) { return false; } diff --git a/packages/rocketchat-message-snippet/server/methods/snippetMessage.js b/packages/rocketchat-message-snippet/server/methods/snippetMessage.js index 6299c81d0b99..d4f590c1c65a 100644 --- a/packages/rocketchat-message-snippet/server/methods/snippetMessage.js +++ b/packages/rocketchat-message-snippet/server/methods/snippetMessage.js @@ -1,6 +1,6 @@ Meteor.methods({ snippetMessage(message, filename) { - if ((typeof Meteor.userId() === 'undefined') || (Meteor.userId() === null)) { + if (Meteor.userId() == null) { //noinspection JSUnresolvedFunction throw new Meteor.Error('error-invalid-user', 'Invalid user', {method: 'snippetMessage'}); @@ -12,7 +12,8 @@ Meteor.methods({ return false; } - if (Array.isArray(room.usernames) && (room.usernames.indexOf(Meteor.user().username) === -1)) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId(), { fields: { _id: 1 } }); + if (!subscription) { return false; } diff --git a/packages/rocketchat-message-star/server/starMessage.js b/packages/rocketchat-message-star/server/starMessage.js index cb8b99ad9eef..230c5af85e2d 100644 --- a/packages/rocketchat-message-star/server/starMessage.js +++ b/packages/rocketchat-message-star/server/starMessage.js @@ -13,12 +13,11 @@ Meteor.methods({ }); } - const room = RocketChat.models.Rooms.findOneById(message.rid); - if (Array.isArray(room.usernames) && room.usernames.indexOf(Meteor.user().username) === -1) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId(), { fields: { _id: 1 } }); + if (!subscription) { return false; } return RocketChat.models.Messages.updateUserStarById(message._id, Meteor.userId(), message.starred); } }); - diff --git a/packages/rocketchat-migrations/migrations.js b/packages/rocketchat-migrations/migrations.js index bc72ae3b8300..ab31856b9233 100644 --- a/packages/rocketchat-migrations/migrations.js +++ b/packages/rocketchat-migrations/migrations.js @@ -272,9 +272,7 @@ Migrations._migrateTo = function(version, rerun) { log.info(`Running ${ direction }() on version ${ migration.version }${ maybeName() }`); try { - RocketChat.models._CacheControl.withValue(false, function() { - migration[direction](migration); - }); + migration[direction](migration); } catch (e) { console.log(makeABox([ 'ERROR! SERVER STOPPED', diff --git a/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js b/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js index ad7e3be83f72..9e4c29460b90 100644 --- a/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js +++ b/packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js @@ -1,5 +1,4 @@ /*global OAuth2Server */ -import _ from 'underscore'; const oauth2server = new OAuth2Server({ accessTokensCollectionName: 'rocketchat_oauth_access_tokens', @@ -80,5 +79,5 @@ RocketChat.API.v1.addAuthMethod(function() { if (user == null) { return; } - return { user: _.omit(user, '$loki') }; + return { user }; }); diff --git a/packages/rocketchat-search/server/events/events.js b/packages/rocketchat-search/server/events/events.js index cb562bf4008b..723764d317a8 100644 --- a/packages/rocketchat-search/server/events/events.js +++ b/packages/rocketchat-search/server/events/events.js @@ -33,20 +33,31 @@ RocketChat.callbacks.add('afterDeleteMessage', function(m) { * Listen to user and room changes via cursor */ -RocketChat.models.Users.on('changed', (type, user)=>{ - if (type === 'inserted' || type === 'updated') { - eventService.promoteEvent('user.save', user._id, user); - } - if (type === 'removed') { - eventService.promoteEvent('user.delete', user._id); + +RocketChat.models.Users.on('change', ({clientAction, id, data}) => { + switch (clientAction) { + case 'updated': + case 'inserted': + const user = data || RocketChat.models.Users.findOneById(id); + eventService.promoteEvent('user.save', id, user); + break; + + case 'removed': + eventService.promoteEvent('user.delete', id); + break; } }); -RocketChat.models.Rooms.on('changed', (type, room)=>{ - if (type === 'inserted' || type === 'updated') { - eventService.promoteEvent('room.save', room._id, room); - } - if (type === 'removed') { - eventService.promoteEvent('room.delete', room._id); +RocketChat.models.Rooms.on('change', ({clientAction, id, data}) => { + switch (clientAction) { + case 'updated': + case 'inserted': + const room = data || RocketChat.models.Rooms.findOneById(id); + eventService.promoteEvent('room.save', id, room); + break; + + case 'removed': + eventService.promoteEvent('room.delete', id); + break; } }); diff --git a/packages/rocketchat-slashcommands-hide/server/hide.js b/packages/rocketchat-slashcommands-hide/server/hide.js index 4ba92586b561..fade32a23d54 100644 --- a/packages/rocketchat-slashcommands-hide/server/hide.js +++ b/packages/rocketchat-slashcommands-hide/server/hide.js @@ -32,7 +32,7 @@ function Hide(command, param, item) { }); } - if (!roomObject.usernames.includes(user.username)) { + if (!RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { fields: { _id: 1 } })) { return RocketChat.Notifications.notifyUser(user._id, 'message', { _id: Random.id(), rid: item.rid, diff --git a/packages/rocketchat-slashcommands-invite/server/server.js b/packages/rocketchat-slashcommands-invite/server/server.js index 2dcd6923c5d2..9e5f10c95de3 100644 --- a/packages/rocketchat-slashcommands-invite/server/server.js +++ b/packages/rocketchat-slashcommands-invite/server/server.js @@ -10,18 +10,19 @@ function Invite(command, params, item) { if (command !== 'invite' || !Match.test(params, String)) { return; } - let usernames = params.replace(/@/g, '').split(/[\s,]/).filter((a) => a !== ''); + const usernames = params.replace(/@/g, '').split(/[\s,]/).filter((a) => a !== ''); if (usernames.length === 0) { return; } - const users = Meteor.users.find({ + let users = Meteor.users.find({ username: { $in: usernames } }); - const currentUser = Meteor.users.findOne(Meteor.userId()); + const userId = Meteor.userId(); + const currentUser = Meteor.users.findOne(userId); if (users.count() === 0) { - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date, @@ -32,24 +33,23 @@ function Invite(command, params, item) { }); return; } - usernames = usernames.filter(function(username) { - if (RocketChat.models.Rooms.findOneByIdContainingUsername(item.rid, username) == null) { + users = users.fetch().filter(function(user) { + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(item.rid, user._id, { fields: { _id: 1 } }); + if (subscription == null) { return true; } - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date, msg: TAPi18n.__('Username_is_already_in_here', { postProcess: 'sprintf', - sprintf: [username] + sprintf: [user.username] }, currentUser.language) }); return false; }); - if (usernames.length === 0) { - return; - } + users.forEach(function(user) { try { @@ -59,14 +59,14 @@ function Invite(command, params, item) { }); } catch ({error}) { if (error === 'cant-invite-for-direct-room') { - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date, msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', null, currentUser.language) }); } else { - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date, diff --git a/packages/rocketchat-slashcommands-inviteall/server/server.js b/packages/rocketchat-slashcommands-inviteall/server/server.js index 133c8200ef18..400b955b5319 100644 --- a/packages/rocketchat-slashcommands-inviteall/server/server.js +++ b/packages/rocketchat-slashcommands-inviteall/server/server.js @@ -16,13 +16,13 @@ function inviteAll(type) { if (!channel) { return; } - - const currentUser = Meteor.users.findOne(Meteor.userId()); + const userId = Meteor.userId(); + const currentUser = Meteor.users.findOne(userId); const baseChannel = type === 'to' ? RocketChat.models.Rooms.findOneById(item.rid) : RocketChat.models.Rooms.findOneByName(channel); const targetChannel = type === 'from' ? RocketChat.models.Rooms.findOneById(item.rid) : RocketChat.models.Rooms.findOneByName(channel); if (!baseChannel) { - return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + return RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date(), @@ -32,18 +32,19 @@ function inviteAll(type) { }, currentUser.language) }); } - const users = baseChannel.usernames || []; + const cursor = RocketChat.models.Subscriptions.findByRoomIdWhenUsernameExists(baseChannel._id, { fields: { 'u.username': 1 } }); try { - if (users.length > RocketChat.settings.get('API_User_Limit')) { + if (cursor.count() > RocketChat.settings.get('API_User_Limit')) { throw new Meteor.Error('error-user-limit-exceeded', 'User Limit Exceeded', { method: 'addAllToRoom' }); } + const users = cursor.fetch().map(s => s.u.username); if (!targetChannel && ['c', 'p'].indexOf(baseChannel.t) > -1) { Meteor.call(baseChannel.t === 'c' ? 'createChannel' : 'createPrivateGroup', channel, users); - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date(), @@ -58,7 +59,7 @@ function inviteAll(type) { users }); } - return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + return RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date(), @@ -66,7 +67,7 @@ function inviteAll(type) { }); } catch (e) { const msg = e.error === 'cant-invite-for-direct-room' ? 'Cannot_invite_users_to_direct_rooms' : e.error; - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date(), diff --git a/packages/rocketchat-slashcommands-join/server/server.js b/packages/rocketchat-slashcommands-join/server/server.js index 1cabe1e1a307..4289ec16bc04 100644 --- a/packages/rocketchat-slashcommands-join/server/server.js +++ b/packages/rocketchat-slashcommands-join/server/server.js @@ -28,7 +28,9 @@ RocketChat.slashCommands.add('join', function Join(command, params, item) { }, user.language) }); } - if (room.usernames.includes(user.username)) { + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { fields: { _id: 1 } }); + if (subscription) { throw new Meteor.Error('error-user-already-in-room', 'You are already in the channel', { method: 'slashCommands' }); diff --git a/packages/rocketchat-slashcommands-kick/server/server.js b/packages/rocketchat-slashcommands-kick/server/server.js index 8b6a298838cf..4fffdc7af62d 100644 --- a/packages/rocketchat-slashcommands-kick/server/server.js +++ b/packages/rocketchat-slashcommands-kick/server/server.js @@ -9,11 +9,12 @@ const Kick = function(command, params, {rid}) { if (username === '') { return; } - const user = Meteor.users.findOne(Meteor.userId()); + const userId = Meteor.userId(); + const user = Meteor.users.findOne(userId); const kickedUser = RocketChat.models.Users.findOneByUsername(username); - const room = RocketChat.models.Rooms.findOneById(rid); + if (kickedUser == null) { - return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + return RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid, ts: new Date, @@ -23,8 +24,10 @@ const Kick = function(command, params, {rid}) { }, user.language) }); } - if ((room.usernames || []).includes(username) === false) { - return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { fields: { _id: 1 } }); + if (!subscription) { + return RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid, ts: new Date, diff --git a/packages/rocketchat-slashcommands-mute/server/mute.js b/packages/rocketchat-slashcommands-mute/server/mute.js index 2c477609d797..357aab64c090 100644 --- a/packages/rocketchat-slashcommands-mute/server/mute.js +++ b/packages/rocketchat-slashcommands-mute/server/mute.js @@ -11,11 +11,11 @@ RocketChat.slashCommands.add('mute', function Mute(command, params, item) { if (username === '') { return; } - const user = Meteor.users.findOne(Meteor.userId()); + const userId = Meteor.userId(); + const user = Meteor.users.findOne(userId); const mutedUser = RocketChat.models.Users.findOneByUsername(username); - const room = RocketChat.models.Rooms.findOneById(item.rid); if (mutedUser == null) { - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date, @@ -26,8 +26,10 @@ RocketChat.slashCommands.add('mute', function Mute(command, params, item) { }); return; } - if ((room.usernames || []).includes(username) === false) { - RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(item.rid, mutedUser._id, { fields: { _id: 1 } }); + if (!subscription) { + RocketChat.Notifications.notifyUser(userId, 'message', { _id: Random.id(), rid: item.rid, ts: new Date, diff --git a/packages/rocketchat-slashcommands-mute/server/unmute.js b/packages/rocketchat-slashcommands-mute/server/unmute.js index 1a864ab63f89..8a471bef50ae 100644 --- a/packages/rocketchat-slashcommands-mute/server/unmute.js +++ b/packages/rocketchat-slashcommands-mute/server/unmute.js @@ -14,7 +14,6 @@ RocketChat.slashCommands.add('unmute', function Unmute(command, params, item) { } const user = Meteor.users.findOne(Meteor.userId()); const unmutedUser = RocketChat.models.Users.findOneByUsername(username); - const room = RocketChat.models.Rooms.findOneById(item.rid); if (unmutedUser == null) { return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { _id: Random.id(), @@ -26,7 +25,9 @@ RocketChat.slashCommands.add('unmute', function Unmute(command, params, item) { }, user.language) }); } - if ((room.usernames || []).includes(username) === false) { + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(item.rid, unmutedUser._id, { fields: { _id: 1 } }); + if (!subscription) { return RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', { _id: Random.id(), rid: item.rid, diff --git a/packages/rocketchat-ui/client/views/app/directory.html b/packages/rocketchat-ui/client/views/app/directory.html index 765a2867110e..e8450d88a69a 100644 --- a/packages/rocketchat-ui/client/views/app/directory.html +++ b/packages/rocketchat-ui/client/views/app/directory.html @@ -18,8 +18,8 @@