[WIP][BREAK] Remove cache layer and internal calculated property `room.usernames` (#10749)

[BREAK] Remove cache layer and internal calculated property `room.usernames`
pull/11408/head^2
Rodrigo Nascimento 8 years ago committed by GitHub
parent 5f04a9496a
commit 9e0a8f50d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      packages/rocketchat-action-links/both/lib/actionLinks.js
  2. 8
      packages/rocketchat-api/server/api.js
  3. 142
      packages/rocketchat-api/server/v1/channels.js
  4. 66
      packages/rocketchat-api/server/v1/groups.js
  5. 39
      packages/rocketchat-api/server/v1/im.js
  6. 8
      packages/rocketchat-api/server/v1/subscriptions.js
  7. 29
      packages/rocketchat-api/server/v1/users.js
  8. 16
      packages/rocketchat-apps/server/bridges/messages.js
  9. 2
      packages/rocketchat-apps/server/bridges/rooms.js
  10. 4
      packages/rocketchat-apps/server/converters/rooms.js
  11. 2
      packages/rocketchat-authorization/server/functions/canAccessRoom.js
  12. 3
      packages/rocketchat-authorization/server/models/Permissions.js
  13. 3
      packages/rocketchat-authorization/server/models/Roles.js
  14. 16
      packages/rocketchat-authorization/server/publications/permissions.js
  15. 1
      packages/rocketchat-cas/server/cas_server.js
  16. 3
      packages/rocketchat-channel-settings/client/views/channelSettings.js
  17. 20
      packages/rocketchat-channel-settings/server/models/Rooms.js
  18. 11
      packages/rocketchat-graphql/server/resolvers/channels/Channel-type.js
  19. 3
      packages/rocketchat-graphql/server/resolvers/channels/channelsByUser.js
  20. 2
      packages/rocketchat-graphql/server/resolvers/users/User-type.js
  21. 5
      packages/rocketchat-integrations/server/lib/triggerHandler.js
  22. 2
      packages/rocketchat-integrations/server/lib/validation.js
  23. 2
      packages/rocketchat-integrations/server/methods/incoming/addIncomingIntegration.js
  24. 2
      packages/rocketchat-integrations/server/methods/incoming/updateIncomingIntegration.js
  25. 2
      packages/rocketchat-integrations/server/processWebhookMessage.js
  26. 3
      packages/rocketchat-lib/client/lib/cachedCollection.js
  27. 1
      packages/rocketchat-lib/client/lib/openRoom.js
  28. 3
      packages/rocketchat-lib/package.js
  29. 8
      packages/rocketchat-lib/server/functions/Notifications.js
  30. 4
      packages/rocketchat-lib/server/functions/addUserToDefaultChannels.js
  31. 5
      packages/rocketchat-lib/server/functions/addUserToRoom.js
  32. 6
      packages/rocketchat-lib/server/functions/createRoom.js
  33. 5
      packages/rocketchat-lib/server/functions/deleteUser.js
  34. 6
      packages/rocketchat-lib/server/functions/removeUserFromRoom.js
  35. 9
      packages/rocketchat-lib/server/functions/sendMessage.js
  36. 2
      packages/rocketchat-lib/server/functions/setRealName.js
  37. 6
      packages/rocketchat-lib/server/lib/debug.js
  38. 2
      packages/rocketchat-lib/server/lib/metrics.js
  39. 43
      packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js
  40. 6
      packages/rocketchat-lib/server/methods/addUsersToRoom.js
  41. 2
      packages/rocketchat-lib/server/methods/getChannelHistory.js
  42. 7
      packages/rocketchat-lib/server/methods/leaveRoom.js
  43. 322
      packages/rocketchat-lib/server/models/Rooms.js
  44. 129
      packages/rocketchat-lib/server/models/Subscriptions.js
  45. 78
      packages/rocketchat-lib/server/models/Users.js
  46. 143
      packages/rocketchat-lib/server/models/_Base.js
  47. 950
      packages/rocketchat-lib/server/models/_BaseCache.js
  48. 150
      packages/rocketchat-lib/server/models/_BaseDb.js
  49. 38
      packages/rocketchat-lib/server/publications/settings.js
  50. 73
      packages/rocketchat-lib/server/startup/cache/CacheLoad.js
  51. 3
      packages/rocketchat-livechat/server/lib/Livechat.js
  52. 6
      packages/rocketchat-livechat/server/lib/QueueMethods.js
  53. 6
      packages/rocketchat-livechat/server/methods/closeRoom.js
  54. 5
      packages/rocketchat-livechat/server/methods/returnAsInquiry.js
  55. 2
      packages/rocketchat-livechat/server/methods/takeInquiry.js
  56. 5
      packages/rocketchat-livechat/server/methods/transfer.js
  57. 5
      packages/rocketchat-livechat/server/publications/visitorHistory.js
  58. 8
      packages/rocketchat-message-pin/client/actionButton.js
  59. 9
      packages/rocketchat-message-pin/server/pinMessage.js
  60. 5
      packages/rocketchat-message-snippet/server/methods/snippetMessage.js
  61. 5
      packages/rocketchat-message-star/server/starMessage.js
  62. 4
      packages/rocketchat-migrations/migrations.js
  63. 3
      packages/rocketchat-oauth2-server-config/oauth/server/oauth2-server.js
  64. 35
      packages/rocketchat-search/server/events/events.js
  65. 2
      packages/rocketchat-slashcommands-hide/server/hide.js
  66. 26
      packages/rocketchat-slashcommands-invite/server/server.js
  67. 17
      packages/rocketchat-slashcommands-inviteall/server/server.js
  68. 4
      packages/rocketchat-slashcommands-join/server/server.js
  69. 13
      packages/rocketchat-slashcommands-kick/server/server.js
  70. 12
      packages/rocketchat-slashcommands-mute/server/mute.js
  71. 5
      packages/rocketchat-slashcommands-mute/server/unmute.js
  72. 4
      packages/rocketchat-ui/client/views/app/directory.html
  73. 10
      packages/rocketchat-ui/client/views/app/directory.js
  74. 2
      packages/rocketchat-user-data-download/server/cronProcessDownloads.js
  75. 1
      server/methods/addAllUserToRoom.js
  76. 32
      server/methods/browseChannels.js
  77. 58
      server/methods/channelsList.js
  78. 5
      server/methods/createDirectMessage.js
  79. 6
      server/methods/getRoomIdByNameOrId.js
  80. 10
      server/methods/getRoomNameById.js
  81. 40
      server/methods/getUsersOfRoom.js
  82. 45
      server/methods/groupsList.js
  83. 3
      server/methods/loadHistory.js
  84. 3
      server/methods/muteUserInRoom.js
  85. 9
      server/methods/removeUserFromRoom.js
  86. 3
      server/methods/unmuteUserInRoom.js
  87. 2
      server/publications/channelAndPrivateAutocomplete.js
  88. 47
      server/publications/room.js
  89. 7
      server/publications/spotlight.js
  90. 25
      server/publications/subscription.js
  91. 4
      server/startup/initialData.js
  92. 120
      server/startup/migrations/v130.js
  93. 92
      server/startup/roomPublishes.js
  94. 23
      server/stream/messages.js
  95. 4
      tests/end-to-end/api/00-miscellaneous.js
  96. 95
      tests/end-to-end/api/04-direct-message.js
  97. 15
      tests/end-to-end/api/11-permissions.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' });
}

@ -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) {

@ -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

@ -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

@ -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
});
}
});

@ -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

@ -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
}
})
});
}
});

@ -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)
);
}
}
}

@ -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;
});

@ -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,

@ -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);
}
}
];

@ -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');

@ -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');

@ -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);
});

@ -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,

@ -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;

@ -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);
};

@ -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',

@ -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
},

@ -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();
}
}
};

@ -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;
}
}

@ -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' });
}
}

@ -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' });
}
}

@ -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' });
}
}

@ -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
}

@ -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'));
}

@ -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);

@ -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');

@ -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) {

@ -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)) {

@ -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,

@ -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) {

@ -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') {

@ -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);
});
}

@ -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;
}

@ -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,

@ -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);

@ -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({

@ -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;

@ -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);
});

@ -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;
}

@ -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);
}
});

@ -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
};

@ -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;
}
}

@ -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: {

@ -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={}) => {

@ -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;

@ -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 = {

@ -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() {

@ -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']}}
]
});

@ -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 });

@ -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);

@ -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' });
}

@ -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});

@ -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);

@ -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' });
}

@ -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' }));
}

@ -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;

@ -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;
}

@ -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;
}

@ -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);
}
});

@ -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',

@ -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 };
});

@ -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;
}
});

@ -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,

@ -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,

@ -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(),

@ -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'
});

@ -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,

@ -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,

@ -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,

@ -18,8 +18,8 @@
<th class="js-sort {{#if searchSortBy 'name'}}is-sorting{{/if}}" data-sort="name">
<div class="table-fake-th"><span>{{_ "Name"}}</span> {{> icon icon=(sortIcon 'name')}}</div>
</th>
<th class="rc-directory-td--users">
<div class="table-fake-th"><span>{{_ "Users"}}</span></div>
<th class="rc-directory-td--users js-sort {{#if searchSortBy 'usersCount'}}is-sorting{{/if}}" data-sort="usersCount">
<div class="table-fake-th"><span>{{_ "Users"}}</span> {{> icon icon=(sortIcon 'usersCount')}}</div>
</th>
{{#if showLastMessage}}
<th class="table-column-date">

@ -2,6 +2,10 @@ import moment from 'moment';
import _ from 'underscore';
function timeAgo(time) {
if (!time) {
return;
}
const now = new Date();
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
@ -14,7 +18,7 @@ function directorySearch(config, cb) {
if (config.type === 'channels') {
return {
name: result.name,
users: (result.usernames ? result.usernames.length : result.usersCount) || 0,
users: result.usersCount || 0,
createdAt: timeAgo(result.ts),
lastMessage: result.lastMessage && timeAgo(result.lastMessage.ts),
description: result.description,
@ -198,8 +202,8 @@ Template.directory.onRendered(function() {
Template.directory.onCreated(function() {
this.searchText = new ReactiveVar('');
this.searchType = new ReactiveVar('channels');
this.searchSortBy = new ReactiveVar('name');
this.sortDirection = new ReactiveVar('asc');
this.searchSortBy = new ReactiveVar('usersCount');
this.sortDirection = new ReactiveVar('desc');
this.limit = new ReactiveVar(0);
this.page = new ReactiveVar(0);
this.end = new ReactiveVar(false);

@ -37,7 +37,7 @@ const loadUserSubscriptions = function(exportOperation) {
const cursor = RocketChat.models.Subscriptions.findByUserId(exportUserId);
cursor.forEach((subscription) => {
const roomId = subscription.rid;
const roomData = subscription._room;
const roomData = RocketChat.models.Rooms.findOneById(roomId);
let roomName = roomData.name ? roomData.name : roomId;
let userId = null;

@ -32,7 +32,6 @@ Meteor.methods({
return;
}
RocketChat.callbacks.run('beforeJoinRoom', user, room);
RocketChat.models.Rooms.addUsernameById(rid, user.username);
RocketChat.models.Subscriptions.createWithRoomAndUser(room, user, {
ts: now,
open: true,

@ -35,7 +35,7 @@ Meteor.methods({
return;
}
if (!['name', 'createdAt', ...type === 'channels' ? ['usernames'] : [], ...type === 'users' ? ['username'] : []].includes(sortBy)) {
if (!['name', 'createdAt', ...type === 'channels' ? ['usersCount'] : [], ...type === 'users' ? ['username'] : []].includes(sortBy)) {
return;
}
@ -56,23 +56,19 @@ Meteor.methods({
return;
}
return {
results: RocketChat.models.Rooms.findByNameAndType(
regex,
'c',
{
...options,
sort,
fields: {
description: 1,
topic: 1,
name: 1,
lastMessage: 1,
ts: 1,
archived: 1,
usernames: 1,
usersCount: 1
}
}).fetch(),
results: RocketChat.models.Rooms.findByNameAndType(regex, 'c', {
...options,
sort,
fields: {
description: 1,
topic: 1,
name: 1,
lastMessage: 1,
ts: 1,
archived: 1,
usersCount: 1
}
}).fetch(),
total: RocketChat.models.Rooms.findByNameAndType(regex, 'c').count()
};
}

@ -44,56 +44,50 @@ Meteor.methods({
}
}
const roomTypes = [];
let channels = [];
const userId = Meteor.userId();
if (channelType !== 'private') {
if (RocketChat.authz.hasPermission(Meteor.userId(), 'view-c-room')) {
roomTypes.push({
type: 'c'
});
} else if (RocketChat.authz.hasPermission(Meteor.userId(), 'view-joined-room')) {
const roomIds = _.pluck(RocketChat.models.Subscriptions.findByTypeAndUserId('c', Meteor.userId()).fetch(), 'rid');
roomTypes.push({
type: 'c',
ids: roomIds
});
if (RocketChat.authz.hasPermission(userId, 'view-c-room')) {
if (filter) {
channels = channels.concat(RocketChat.models.Rooms.findByType('c', options).fetch());
} else {
channels = channels.concat(RocketChat.models.Rooms.findByTypeAndNameContaining('c', filter, options).fetch());
}
} else if (RocketChat.authz.hasPermission(userId, 'view-joined-room')) {
const roomIds = RocketChat.models.Subscriptions.findByTypeAndUserId('c', userId, {fields: {rid: 1}}).fetch().map(s => s.rid);
if (filter) {
channels = channels.concat(RocketChat.models.Rooms.findByTypeInIds('c', roomIds, options).fetch());
} else {
channels = channels.concat(RocketChat.models.Rooms.findByTypeInIdsAndNameContaining('c', roomIds, filter, options).fetch());
}
}
}
if (channelType !== 'public' && RocketChat.authz.hasPermission(Meteor.userId(), 'view-p-room')) {
const user = RocketChat.models.Users.findOne(Meteor.userId(), {
if (channelType !== 'public' && RocketChat.authz.hasPermission(userId, 'view-p-room')) {
const user = RocketChat.models.Users.findOne(userId, {
fields: {
username: 1,
'settings.preferences.sidebarGroupByType': 1
}
});
const userPref = RocketChat.getUserPreference(user, 'sidebarGroupByType');
const globalPref = RocketChat.settings.get('UI_Group_Channels_By_Type');
// needs to negate globalPref because userPref represents its opposite
const groupByType = userPref !== undefined ? userPref : globalPref;
const groupByType = userPref !== undefined ? userPref : RocketChat.settings.get('UI_Group_Channels_By_Type');
if (!groupByType) {
roomTypes.push({
type: 'p',
username: user.username
});
}
}
if (roomTypes.length) {
if (filter) {
return {
channels: RocketChat.models.Rooms.findByNameContainingTypesWithUsername(filter, roomTypes, options).fetch()
};
const roomIds = RocketChat.models.Subscriptions.findByTypeAndUserId('p', userId, {fields: {rid: 1}}).fetch().map(s => s.rid);
if (filter) {
channels = channels.concat(RocketChat.models.Rooms.findByTypeInIds('p', roomIds, options).fetch());
} else {
channels = channels.concat(RocketChat.models.Rooms.findByTypeInIdsAndNameContaining('p', roomIds, filter, options).fetch());
}
}
return {
channels: RocketChat.models.Rooms.findContainingTypesWithUsername(roomTypes, options).fetch()
};
}
return {
channels: []
channels
};
}
});

@ -50,7 +50,8 @@ Meteor.methods({
$setOnInsert: {
t: 'd',
msgs: 0,
ts: now
ts: now,
usersCount: 2
}
});
@ -64,6 +65,7 @@ Meteor.methods({
open: true
},
$setOnInsert: {
fname: to.name,
name: to.username,
t: 'd',
alert: false,
@ -95,6 +97,7 @@ Meteor.methods({
$and: [{'u._id': to._id}] // work around to solve problems with upsert and dot
}, {
$setOnInsert: {
fname: me.username,
name: me.username,
t: 'd',
open: false,

@ -1,3 +1,4 @@
// DEPRECATE
Meteor.methods({
getRoomIdByNameOrId(rid) {
check(rid, String);
@ -16,11 +17,6 @@ Meteor.methods({
});
}
const user = Meteor.user();
if (user && user.username && room.usernames.indexOf(user.username) !== -1) {
return room._id;
}
if (room.t !== 'c' || RocketChat.authz.hasPermission(Meteor.userId(), 'view-c-room') !== true) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'getRoomIdByNameOrId'

@ -1,8 +1,8 @@
Meteor.methods({
getRoomNameById(rid) {
check(rid, String);
if (!Meteor.userId()) {
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'getRoomNameById'
});
@ -16,12 +16,12 @@ Meteor.methods({
});
}
const user = Meteor.user();
if (user && user.username && room.usernames.indexOf(user.username) !== -1) {
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, userId, { fields: { _id: 1 } });
if (subscription) {
return room.name;
}
if (room.t !== 'c' || RocketChat.authz.hasPermission(Meteor.userId(), 'view-c-room') !== true) {
if (room.t !== 'c' || RocketChat.authz.hasPermission(userId, 'view-c-room') !== true) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'getRoomNameById'
});

@ -1,44 +1,30 @@
Meteor.methods({
getUsersOfRoom(roomId, showAll) {
if (!Meteor.userId()) {
getUsersOfRoom(rid, showAll) {
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getUsersOfRoom' });
}
const room = Meteor.call('canAccessRoom', roomId, Meteor.userId());
const room = Meteor.call('canAccessRoom', rid, userId);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getUsersOfRoom' });
}
if (room.broadcast && !RocketChat.authz.hasPermission(Meteor.userId(), 'view-broadcast-member-list', roomId)) {
if (room.broadcast && !RocketChat.authz.hasPermission(userId, 'view-broadcast-member-list', rid)) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getUsersOfRoom' });
}
const filter = (record) => {
if (!record._user) {
console.log('Subscription without user', record._id);
return false;
}
const subscriptions = RocketChat.models.Subscriptions.findByRoomIdWhenUsernameExists(rid, { fields: { 'u._id': 1 } }).fetch();
const userIds = subscriptions.map(s => s.u._id); // TODO: CACHE: expensive
const options = { fields: { username: 1, name: 1 } };
if (showAll === true) {
return true;
}
return record._user.status !== 'offline';
};
const map = (record) => {
return {
_id: record._user._id,
username: record._user.username,
name: record._user.name
};
};
const records = RocketChat.models.Subscriptions.findByRoomId(roomId).fetch();
const users = showAll === true
? RocketChat.models.Users.findUsersWithUsernameByIds(userIds, options).fetch()
: RocketChat.models.Users.findUsersWithUsernameByIdsNotOffline(userIds, options).fetch();
return {
total: records.length,
records: records.filter(filter).map(map)
total: userIds.length,
records: users
};
}
});

@ -1,45 +0,0 @@
import _ from 'underscore';
import s from 'underscore.string';
Meteor.methods({
groupsList(nameFilter, limit, sort) {
check(nameFilter, Match.Optional(String));
check(limit, Match.Optional(Number));
check(sort, Match.Optional(String));
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'groupsList' });
}
const options = {
fields: { name: 1 },
sort: { name: 1 }
};
//Verify the limit param is a number
if (_.isNumber(limit)) {
options.limit = limit;
}
//Verify there is a sort option and it's a string
if (s.trim(sort)) {
switch (sort) {
case 'name':
options.sort = { name: 1 };
break;
case 'msgs':
options.sort = { msgs: -1 };
break;
}
}
//Determine if they are searching or not, base it upon the name field
if (nameFilter) {
return { groups: RocketChat.models.Rooms.findByTypeAndNameContainingUsername('p', new RegExp(s.trim(s.escapeRegExp(nameFilter)), 'i'), Meteor.user().username, options).fetch() };
} else {
const roomIds = _.pluck(RocketChat.models.Subscriptions.findByTypeAndUserId('p', Meteor.userId()).fetch(), 'rid');
return { groups: RocketChat.models.Rooms.findByIds(roomIds, options).fetch() };
}
}
});

@ -37,7 +37,8 @@ Meteor.methods({
const canAnonymous = RocketChat.settings.get('Accounts_AllowAnonymousRead');
const canPreview = RocketChat.authz.hasPermission(fromId, 'preview-c-room');
if (room.t === 'c' && !canAnonymous && !canPreview && room.usernames.indexOf(room.username) === -1) {
if (room.t === 'c' && !canAnonymous && !canPreview && RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, fromId, { fields: { _id: 1 } })) {
return false;
}

@ -34,7 +34,8 @@ Meteor.methods({
});
}
if (Array.isArray(room.usernames) === false || room.usernames.includes(data.username) === false) {
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUsername(data.rid, data.username, { fields: { _id: 1 } });
if (!subscription) {
throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
method: 'muteUserInRoom'
});

@ -27,14 +27,15 @@ Meteor.methods({
});
}
if (Array.isArray(room.usernames) === false || room.usernames.includes(data.username) === false) {
const removedUser = RocketChat.models.Users.findOneByUsername(data.username);
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(data.rid, removedUser._id, { fields: { _id: 1 } });
if (!subscription) {
throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
method: 'removeUserFromRoom'
});
}
const removedUser = RocketChat.models.Users.findOneByUsername(data.username);
if (RocketChat.authz.hasRole(removedUser._id, 'owner', room._id)) {
const numOwners = RocketChat.authz.getUsersInRole('owner', room._id).fetch().length;
@ -45,8 +46,6 @@ Meteor.methods({
}
}
RocketChat.models.Rooms.removeUsernameById(data.rid, data.username);
RocketChat.models.Subscriptions.removeByRoomIdAndUserId(data.rid, removedUser._id);
if (['c', 'p'].includes(room.t) === true) {

@ -28,7 +28,8 @@ Meteor.methods({
});
}
if (Array.isArray(room.usernames) === false || room.usernames.includes(data.username) === false) {
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUsername(data.rid, data.username, { fields: { _id: 1 } });
if (!subscription) {
throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
method: 'unmuteUserInRoom'
});

@ -19,7 +19,7 @@ Meteor.publish('channelAndPrivateAutocomplete', function(selector) {
}
};
const cursorHandle = RocketChat.models.Rooms.findByNameStartingAndTypes(selector.name, ['c', 'p'], options).observeChanges({
const cursorHandle = RocketChat.models.Rooms.findChannelAndPrivateByNameStarting(selector.name, options).observeChanges({
added(_id, record) {
return pub.added('autocompleteRecords', _id, record);
},

@ -40,10 +40,9 @@ const fields = {
};
const roomMap = (record) => {
if (record._room) {
return _.pick(record._room, ...Object.keys(fields));
if (record) {
return _.pick(record, ...Object.keys(fields));
}
console.log('Empty Room for Subscription', record);
return {};
};
@ -75,7 +74,9 @@ Meteor.methods({
},
getRoomByTypeAndName(type, name) {
if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
const userId = Meteor.userId();
if (!userId && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getRoomByTypeAndName' });
}
@ -89,36 +90,40 @@ Meteor.methods({
room = RocketChat.models.Rooms.findByTypeAndName(type, name).fetch();
}
if (!room) {
if (!room || room.length === 0) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getRoomByTypeAndName' });
}
if (!Meteor.call('canAccessRoom', room._id, Meteor.userId())) {
room = room[0];
if (!Meteor.call('canAccessRoom', room._id, userId)) {
throw new Meteor.Error('error-no-permission', 'No permission', { method: 'getRoomByTypeAndName' });
}
if (RocketChat.settings.get('Store_Last_Message') && !RocketChat.authz.hasPermission(Meteor.userId(), 'preview-c-room')) {
if (RocketChat.settings.get('Store_Last_Message') && !RocketChat.authz.hasPermission(userId, 'preview-c-room')) {
delete room.lastMessage;
}
return roomMap({ _room: room });
return roomMap(room);
}
});
RocketChat.models.Rooms.cache.on('sync', (type, room/*, diff*/) => {
const records = RocketChat.models.Subscriptions.findByRoomId(room._id).fetch();
const _room = roomMap({_room: room});
for (const record of records) {
RocketChat.Notifications.notifyUserInThisInstance(record.u._id, 'rooms-changed', type, _room);
RocketChat.models.Rooms.on('change', ({clientAction, id, data}) => {
switch (clientAction) {
case 'updated':
case 'inserted':
// Override data cuz we do not publish all fields
data = RocketChat.models.Rooms.findOneById(id, { fields });
break;
case 'removed':
data = { _id: id };
break;
}
});
RocketChat.models.Subscriptions.on('changed', (type, subscription/*, diff*/) => {
if (type === 'inserted' || type === 'removed') {
const room = RocketChat.models.Rooms.findOneById(subscription.rid);
if (room) {
RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'rooms-changed', type, roomMap({_room: room}));
}
if (data) {
RocketChat.models.Subscriptions.findByRoomId(id, {fields: {'u._id': 1}}).forEach(({u}) => {
RocketChat.Notifications.notifyUserInThisInstance(u._id, 'rooms-changed', clientAction, data);
});
}
});

@ -67,15 +67,12 @@ Meteor.methods({
}
if (type.rooms === true && RocketChat.authz.hasPermission(userId, 'view-c-room')) {
const username = RocketChat.models.Users.findOneById(userId, {
username: 1
}).username;
const searchableRoomTypes = Object.entries(RocketChat.roomTypes.roomTypes)
.filter((roomType)=>roomType[1].includeInRoomSearch())
.map((roomType)=>roomType[0]);
result.rooms = fetchRooms(userId, RocketChat.models.Rooms.findByNameAndTypesNotContainingUsername(regex, searchableRoomTypes, username, roomOptions).fetch());
const roomIds = RocketChat.models.Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypes, {fields: {rid: 1}}).fetch().map(s => s.rid);
result.rooms = fetchRooms(userId, RocketChat.models.Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypes, roomIds, roomOptions).fetch());
}
} else if (type.users === true && rid) {
const subscriptions = RocketChat.models.Subscriptions.find({

@ -65,17 +65,18 @@ Meteor.methods({
}
});
RocketChat.models.Subscriptions.on('changed', function(type, subscription) {
RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'subscriptions-changed', type, RocketChat.models.Subscriptions.processQueryOptionsOnResult(subscription, {
fields
}));
});
RocketChat.models.Subscriptions.on('change', ({clientAction, id, data}) => {
switch (clientAction) {
case 'updated':
case 'inserted':
// Override data cuz we do not publish all fields
data = RocketChat.models.Subscriptions.findOneById(id, { fields });
break;
case 'removed':
data = RocketChat.models.Subscriptions.trashFindOneById(id, { fields: { u: 1 } });
break;
}
// TODO needs improvement
// We are sending the record again cuz any update on subscription will send the record without the fname (join)
// Then we need to sent it again listening to the join event.
RocketChat.models.Subscriptions.on('join:fname:inserted', function(subscription/*, user*/) {
RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'subscriptions-changed', 'changed', RocketChat.models.Subscriptions.processQueryOptionsOnResult(subscription, {
fields
}));
RocketChat.Notifications.notifyUserInThisInstance(data.u._id, 'subscriptions-changed', clientAction, data);
});

@ -1,7 +1,7 @@
import _ from 'underscore';
Meteor.startup(function() {
Meteor.defer(() => RocketChat.models._CacheControl.withValue(false, function() {
Meteor.defer(() => {
if (!RocketChat.models.Rooms.findOneById('GENERAL')) {
RocketChat.models.Rooms.createWithIdTypeAndName('GENERAL', 'c', 'general', {
'default': true
@ -201,5 +201,5 @@ Meteor.startup(function() {
return RocketChat.addUserToDefaultChannels(adminUser, true);
}
}));
});
});

@ -0,0 +1,120 @@
import Future from 'fibers/future';
RocketChat.Migrations.add({
version: 129,
up() {
RocketChat.models.Rooms._db.originals.update(
{
t: { $ne: 'd' }
},
{
$unset: { usernames: 1 }
},
{
multi: true
}
);
RocketChat.models.Rooms.find(
{
usersCount: { $exists: false }
},
{
fields: {
_id: 1
}
}
).forEach(({ _id }) => {
const usersCount = RocketChat.models.Subscriptions.findByRoomId(
_id
).count();
RocketChat.models.Rooms._db.originals.update(
{
_id
},
{
$set: {
usersCount
}
}
);
});
// Getting all subscriptions and users to memory allow us to process in batches,
// all other solutions takes hundreds or thousands times more to process.
const subscriptions = RocketChat.models.Subscriptions.find(
{
t: 'd',
name: { $exists: true },
fname: { $exists: false }
},
{
fields: {
name: 1
}
}
).fetch();
const users = RocketChat.models.Users.find(
{ username: { $exists: true }, name: { $exists: true } },
{ fields: { username: 1, name: 1 } }
).fetch();
const usersByUsername = users.reduce((obj, user) => {
obj[user.username] = user.name;
return obj;
}, {});
const updateSubscription = subscription => {
return new Promise(resolve => {
Meteor.defer(() => {
const name = usersByUsername[subscription.name];
if (!name) {
return resolve();
}
RocketChat.models.Subscriptions._db.originals.update(
{
_id: subscription._id
},
{
$set: {
fname: name
}
}
);
resolve();
});
});
};
// Use FUTURE to process itens in batchs and wait the final one
const fut = new Future();
const processBatch = () => {
const itens = subscriptions.splice(0, 1000);
console.log(
'Migrating',
itens.length,
'of',
subscriptions.length,
'subscriptions'
);
if (itens.length) {
Promise.all(itens.map(s => updateSubscription(s))).then(() => {
processBatch();
});
} else {
fut.return();
}
};
processBatch();
fut.wait();
}
});

@ -1,92 +0,0 @@
Meteor.startup(function() {
RocketChat.roomTypes.setPublish('c', function(identifier) {
const options = {
fields: {
name: 1,
t: 1,
cl: 1,
u: 1,
usernames: 1,
topic: 1,
announcement: 1,
muted: 1,
archived: 1,
ro: 1,
reactWhenReadOnly: 1,
jitsiTimeout: 1,
description: 1,
sysMes: 1,
joinCodeRequired: 1,
streamingOptions: 1
}
};
if (RocketChat.authz.hasPermission(this.userId, 'view-c-room')) {
return RocketChat.models.Rooms.findByTypeAndName('c', identifier, options);
} else if (RocketChat.authz.hasPermission(this.userId, 'view-joined-room')) {
const roomId = RocketChat.models.Subscriptions.findByTypeNameAndUserId('c', identifier, this.userId).fetch();
if (roomId.length > 0) {
return RocketChat.models.Rooms.findById(roomId[0].rid, options);
}
}
return this.ready();
});
RocketChat.roomTypes.setPublish('p', function(identifier) {
const options = {
fields: {
name: 1,
t: 1,
cl: 1,
u: 1,
usernames: 1,
topic: 1,
announcement: 1,
muted: 1,
archived: 1,
ro: 1,
reactWhenReadOnly: 1,
jitsiTimeout: 1,
description: 1,
sysMes: 1,
tokenpass: 1,
streamingOptions: 1
}
};
const user = RocketChat.models.Users.findOneById(this.userId, {
fields: {
username: 1
}
});
return RocketChat.models.Rooms.findByTypeAndNameContainingUsername('p', identifier, user.username, options);
});
return RocketChat.roomTypes.setPublish('d', function(identifier) {
const options = {
fields: {
name: 1,
t: 1,
cl: 1,
u: 1,
usernames: 1,
topic: 1,
jitsiTimeout: 1
}
};
const user = RocketChat.models.Users.findOneById(this.userId, {
fields: {
username: 1
}
});
if (RocketChat.authz.hasAtLeastOnePermission(this.userId, ['view-d-room', 'view-joined-room'])) {
return RocketChat.models.Rooms.findByTypeContainingUsernames('d', [user.username, identifier], options);
}
return this.ready();
});
});

@ -11,7 +11,7 @@ msgStream.allowRead(function(eventName, args) {
return false;
}
if (room.t === 'c' && !RocketChat.authz.hasPermission(this.userId, 'preview-c-room') && room.usernames.indexOf(room.username) === -1) {
if (room.t === 'c' && !RocketChat.authz.hasPermission(this.userId, 'preview-c-room') && !RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) {
return false;
}
@ -32,7 +32,7 @@ msgStream.allowEmit('__my_messages__', function(eventName, msg, options) {
return false;
}
options.roomParticipant = room.usernames.indexOf(room.username) > -1;
options.roomParticipant = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }) != null;
options.roomType = room.t;
return true;
@ -63,19 +63,12 @@ Meteor.startup(function() {
}
}
return RocketChat.models.Messages._db.on('change', function({action, id, data/*, oplog*/}) {
switch (action) {
case 'insert':
data._id = id;
publishMessage('inserted', data);
break;
case 'update:record':
publishMessage('updated', data);
break;
case 'update:diff':
publishMessage('updated', RocketChat.models.Messages.findOne({
_id: id
}));
return RocketChat.models.Messages.on('change', function({ clientAction, id, data/*, oplog*/ }) {
switch (clientAction) {
case 'inserted':
case 'updated':
const message = data || RocketChat.models.Messages.findOne({ _id: id });
publishMessage(clientAction, message);
break;
}
});

@ -205,7 +205,7 @@ describe('miscellaneous', function() {
expect(res.body).to.have.property('result').and.to.be.an('array');
expect(res.body.result[0]).to.have.property('_id');
expect(res.body.result[0]).to.have.property('name');
expect(res.body.result[0]).to.have.property('usernames').and.to.be.an('array');
expect(res.body.result[0]).to.have.property('usersCount').and.to.be.an('number');
expect(res.body.result[0]).to.have.property('ts');
})
.end(done);
@ -232,7 +232,7 @@ describe('miscellaneous', function() {
expect(res.body).to.have.property('result').and.to.be.an('array');
expect(res.body.result[0]).to.have.property('_id');
expect(res.body.result[0]).to.have.property('name');
expect(res.body.result[0]).to.have.property('usernames').and.to.be.an('array');
expect(res.body.result[0]).to.have.property('usersCount').and.to.be.an('number');
expect(res.body.result[0]).to.have.property('ts');
})
.end(done);

@ -2,7 +2,7 @@
/* globals expect */
/* eslint no-unused-vars: 0 */
import {getCredentials, api, login, request, credentials, directMessage, log } from '../../data/api-data.js';
import {getCredentials, api, login, request, credentials, directMessage, log, apiUsername, apiEmail } from '../../data/api-data.js';
import {adminEmail, password} from '../../data/user.js';
import supertest from 'supertest';
@ -136,4 +136,97 @@ describe('[Direct Messages]', function() {
})
.end(done);
});
describe('fname property', () => {
const username = `fname_${ apiUsername }`;
const name = `Name fname_${ apiUsername }`;
const updatedName = `Updated Name fname_${ apiUsername }`;
const email = `fname_${ apiEmail }`;
let userId;
let directMessageId;
before((done) => {
request.post(api('users.create'))
.set(credentials)
.send({
email,
name,
username,
password,
active: true,
roles: ['user'],
joinDefaultChannels: true,
verified: true
})
.expect((res) => {
userId = res.body.user._id;
})
.end(done);
});
before((done) => {
request.post(api('chat.postMessage'))
.set(credentials)
.send({
channel: `@${ username }`,
text: 'This message was sent using the API'
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message.msg', 'This message was sent using the API');
expect(res.body).to.have.nested.property('message.rid');
directMessageId = res.body.message.rid;
})
.end(done);
});
it('should have fname property', (done) => {
request.get(api('subscriptions.getOne'))
.set(credentials)
.query({
roomId: directMessageId
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body.subscription).to.have.property('name', username);
expect(res.body.subscription).to.have.property('fname', name);
})
.end(done);
});
it('should update user\'s name', (done) => {
request.post(api('users.update'))
.set(credentials)
.send({
userId,
data: {
name: updatedName
}
})
.expect((res) => {
expect(res.body.user).to.have.property('name', updatedName);
})
.end(done);
});
it('should have fname property updated', (done) => {
request.get(api('subscriptions.getOne'))
.set(credentials)
.query({
roomId: directMessageId
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body.subscription).to.have.property('name', username);
expect(res.body.subscription).to.have.property('fname', updatedName);
})
.end(done);
});
});
});

@ -23,11 +23,6 @@ describe('[Permissions]', function() {
expect(firstElement).to.have.property('_id');
expect(firstElement).to.have.property('roles').and.to.be.a('array');
expect(firstElement).to.have.property('_updatedAt');
expect(firstElement).to.have.property('meta');
expect(firstElement.meta).to.have.property('revision');
expect(firstElement.meta).to.have.property('created');
expect(firstElement.meta).to.have.property('version');
expect(firstElement).to.have.property('$loki');
})
.end(done);
});
@ -47,11 +42,6 @@ describe('[Permissions]', function() {
expect(firstElement).to.have.property('_id');
expect(firstElement).to.have.property('roles').and.to.be.a('array');
expect(firstElement).to.have.property('_updatedAt');
expect(firstElement).to.have.property('meta');
expect(firstElement.meta).to.have.property('revision');
expect(firstElement.meta).to.have.property('created');
expect(firstElement.meta).to.have.property('version');
expect(firstElement).to.have.property('$loki');
})
.end(done);
});
@ -78,11 +68,6 @@ describe('[Permissions]', function() {
expect(firstElement).to.have.property('_id');
expect(firstElement).to.have.property('roles').and.to.be.a('array');
expect(firstElement).to.have.property('_updatedAt');
expect(firstElement).to.have.property('meta');
expect(firstElement.meta).to.have.property('revision');
expect(firstElement.meta).to.have.property('created');
expect(firstElement.meta).to.have.property('version');
expect(firstElement).to.have.property('$loki');
})
.end(done);
});

Loading…
Cancel
Save