The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
Rocket.Chat/packages/rocketchat-lib/server/models/Messages.js

833 lines
16 KiB

import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import _ from 'underscore';
RocketChat.models.Messages = new class extends RocketChat.models._Base {
constructor() {
super('message');
this.tryEnsureIndex({ rid: 1, ts: 1 });
this.tryEnsureIndex({ ts: 1 });
this.tryEnsureIndex({ 'u._id': 1 });
this.tryEnsureIndex({ editedAt: 1 }, { sparse: 1 });
this.tryEnsureIndex({ 'editedBy._id': 1 }, { sparse: 1 });
this.tryEnsureIndex({ rid: 1, t: 1, 'u._id': 1 });
this.tryEnsureIndex({ expireAt: 1 }, { expireAfterSeconds: 0 });
this.tryEnsureIndex({ msg: 'text' });
this.tryEnsureIndex({ 'file._id': 1 }, { sparse: 1 });
this.tryEnsureIndex({ 'mentions.username': 1 }, { sparse: 1 });
this.tryEnsureIndex({ pinned: 1 }, { sparse: 1 });
this.tryEnsureIndex({ snippeted: 1 }, { sparse: 1 });
this.tryEnsureIndex({ location: '2dsphere' });
this.tryEnsureIndex({ slackBotId: 1, slackTs: 1 }, { sparse: 1 });
}
countVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$gte: afterTimestamp,
$lte: beforeTimestamp,
},
};
return this.find(query, options).count();
}
// FIND
findByMention(username, options) {
const query = { 'mentions.username': username };
return this.find(query, options);
}
findFilesByUserId(userId, options = {}) {
const query = {
'u._id': userId,
'file._id': { $exists: true },
};
return this.find(query, { fields: { 'file._id': 1 }, ...options });
}
findFilesByRoomIdPinnedTimestampAndUsers(rid, excludePinned, ts, users = [], options = {}) {
const query = {
rid,
ts,
'file._id': { $exists: true },
};
if (excludePinned) {
query.pinned = { $ne: true };
}
if (users.length) {
query['u.username'] = { $in: users };
}
return this.find(query, { fields: { 'file._id': 1 }, ...options });
}
findVisibleByMentionAndRoomId(username, rid, options) {
const query = {
_hidden: { $ne: true },
'mentions.username': username,
rid,
};
return this.find(query, options);
}
findVisibleByRoomId(roomId, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
};
return this.find(query, options);
}
findVisibleByRoomIdNotContainingTypes(roomId, types, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
};
if (Match.test(types, [String]) && (types.length > 0)) {
query.t =
{ $nin: types };
}
return this.find(query, options);
}
findInvisibleByRoomId(roomId, options) {
const query = {
_hidden: true,
rid: roomId,
};
return this.find(query, options);
}
findVisibleByRoomIdAfterTimestamp(roomId, timestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$gt: timestamp,
},
};
return this.find(query, options);
}
findForUpdates(roomId, timestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
_updatedAt: {
$gt: timestamp,
},
};
return this.find(query, options);
}
findVisibleByRoomIdBeforeTimestamp(roomId, timestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$lt: timestamp,
},
};
return this.find(query, options);
}
findVisibleByRoomIdBeforeTimestampInclusive(roomId, timestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$lte: timestamp,
},
};
return this.find(query, options);
}
findVisibleByRoomIdBetweenTimestamps(roomId, afterTimestamp, beforeTimestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$gt: afterTimestamp,
$lt: beforeTimestamp,
},
};
return this.find(query, options);
}
findVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$gte: afterTimestamp,
$lte: beforeTimestamp,
},
};
return this.find(query, options);
}
findVisibleByRoomIdBeforeTimestampNotContainingTypes(roomId, timestamp, types, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$lt: timestamp,
},
};
if (Match.test(types, [String]) && (types.length > 0)) {
query.t =
{ $nin: types };
}
return this.find(query, options);
}
findVisibleByRoomIdBetweenTimestampsNotContainingTypes(roomId, afterTimestamp, beforeTimestamp, types, options) {
const query = {
_hidden: {
$ne: true,
},
rid: roomId,
ts: {
$gt: afterTimestamp,
$lt: beforeTimestamp,
},
};
if (Match.test(types, [String]) && (types.length > 0)) {
query.t =
{ $nin: types };
}
return this.find(query, options);
}
findVisibleCreatedOrEditedAfterTimestamp(timestamp, options) {
const query = {
_hidden: { $ne: true },
$or: [{
ts: {
$gt: timestamp,
},
},
{
editedAt: {
$gt: timestamp,
},
},
],
};
return this.find(query, options);
}
findStarredByUserAtRoom(userId, roomId, options) {
const query = {
_hidden: { $ne: true },
'starred._id': userId,
rid: roomId,
};
return this.find(query, options);
}
findPinnedByRoom(roomId, options) {
const query = {
t: { $ne: 'rm' },
_hidden: { $ne: true },
pinned: true,
rid: roomId,
};
return this.find(query, options);
}
findSnippetedByRoom(roomId, options) {
const query = {
_hidden: { $ne: true },
snippeted: true,
rid: roomId,
};
return this.find(query, options);
}
getLastTimestamp(options) {
if (options == null) { options = {}; }
const query = { ts: { $exists: 1 } };
options.sort = { ts: -1 };
options.limit = 1;
const [message] = this.find(query, options).fetch();
return message && message.ts;
}
findByRoomIdAndMessageIds(rid, messageIds, options) {
const query = {
rid,
_id: {
$in: messageIds,
},
};
return this.find(query, options);
}
findOneBySlackBotIdAndSlackTs(slackBotId, slackTs) {
const query = {
slackBotId,
slackTs,
};
return this.findOne(query);
}
findOneBySlackTs(slackTs) {
const query = { slackTs };
return this.findOne(query);
}
findByRoomIdAndType(roomId, type, options) {
const query = {
rid: roomId,
t: type,
};
if (options == null) { options = {}; }
return this.find(query, options);
}
findByRoomId(roomId, options) {
const query = {
rid: roomId,
};
return this.find(query, options);
}
getLastVisibleMessageSentWithNoTypeByRoomId(rid, messageId) {
const query = {
rid,
_hidden: { $ne: true },
t: { $exists: false },
};
if (messageId) {
query._id = { $ne: messageId };
}
const options = {
sort: {
ts: -1,
},
};
return this.findOne(query, options);
}
cloneAndSaveAsHistoryById(_id) {
const me = RocketChat.models.Users.findOneById(Meteor.userId());
const record = this.findOneById(_id);
record._hidden = true;
record.parent = record._id;
record.editedAt = new Date;
record.editedBy = {
_id: Meteor.userId(),
username: me.username,
};
delete record._id;
return this.insert(record);
}
// UPDATE
setHiddenById(_id, hidden) {
if (hidden == null) { hidden = true; }
const query = { _id };
const update = {
$set: {
_hidden: hidden,
},
};
return this.update(query, update);
}
setAsDeletedByIdAndUser(_id, user) {
const query = { _id };
const update = {
$set: {
msg: '',
t: 'rm',
urls: [],
mentions: [],
attachments: [],
reactions: [],
editedAt: new Date(),
editedBy: {
_id: user._id,
username: user.username,
},
},
};
return this.update(query, update);
}
setPinnedByIdAndUserId(_id, pinnedBy, pinned, pinnedAt) {
if (pinned == null) { pinned = true; }
if (pinnedAt == null) { pinnedAt = 0; }
const query = { _id };
const update = {
$set: {
pinned,
pinnedAt: pinnedAt || new Date,
pinnedBy,
},
};
return this.update(query, update);
}
setSnippetedByIdAndUserId(message, snippetName, snippetedBy, snippeted, snippetedAt) {
if (snippeted == null) { snippeted = true; }
if (snippetedAt == null) { snippetedAt = 0; }
const query = { _id: message._id };
const msg = `\`\`\`${ message.msg }\`\`\``;
const update = {
$set: {
msg,
snippeted,
snippetedAt: snippetedAt || new Date,
snippetedBy,
snippetName,
},
};
return this.update(query, update);
}
setUrlsById(_id, urls) {
const query = { _id };
const update = {
$set: {
urls,
},
};
return this.update(query, update);
}
updateAllUsernamesByUserId(userId, username) {
const query = { 'u._id': userId };
const update = {
$set: {
'u.username': username,
},
};
return this.update(query, update, { multi: true });
}
updateUsernameOfEditByUserId(userId, username) {
const query = { 'editedBy._id': userId };
const update = {
$set: {
'editedBy.username': username,
},
};
return this.update(query, update, { multi: true });
}
updateUsernameAndMessageOfMentionByIdAndOldUsername(_id, oldUsername, newUsername, newMessage) {
const query = {
_id,
'mentions.username': oldUsername,
};
const update = {
$set: {
'mentions.$.username': newUsername,
msg: newMessage,
},
};
return this.update(query, update);
}
updateUserStarById(_id, userId, starred) {
let update;
const query = { _id };
if (starred) {
update = {
$addToSet: {
starred: { _id: userId },
},
};
} else {
update = {
$pull: {
starred: { _id: Meteor.userId() },
},
};
}
return this.update(query, update);
}
upgradeEtsToEditAt() {
const query = { ets: { $exists: 1 } };
const update = {
$rename: {
ets: 'editedAt',
},
};
return this.update(query, update, { multi: true });
}
setMessageAttachments(_id, attachments) {
const query = { _id };
const update = {
$set: {
attachments,
},
};
return this.update(query, update);
}
setSlackBotIdAndSlackTs(_id, slackBotId, slackTs) {
const query = { _id };
const update = {
$set: {
slackBotId,
slackTs,
},
};
return this.update(query, update);
}
unlinkUserId(userId, newUserId, newUsername, newNameAlias) {
const query = {
'u._id': userId,
};
const update = {
$set: {
alias: newNameAlias,
'u._id': newUserId,
'u.username' : newUsername,
'u.name' : undefined,
},
};
return this.update(query, update, { multi: true });
}
// INSERT
createWithTypeRoomIdMessageAndUser(type, roomId, message, user, extraData) {
const room = RocketChat.models.Rooms.findOneById(roomId, { fields: { sysMes: 1 } });
if ((room != null ? room.sysMes : undefined) === false) {
return;
}
const record = {
t: type,
rid: roomId,
ts: new Date,
msg: message,
u: {
_id: user._id,
username: user.username,
},
groupable: false,
};
if (RocketChat.settings.get('Message_Read_Receipt_Enabled')) {
record.unread = true;
}
_.extend(record, extraData);
record._id = this.insertOrUpsert(record);
RocketChat.models.Rooms.incMsgCountById(room._id, 1);
return record;
}
createNavigationHistoryWithRoomIdMessageAndUser(roomId, message, user, extraData) {
const type = 'livechat_navigation_history';
const room = RocketChat.models.Rooms.findOneById(roomId, { fields: { sysMes: 1 } });
if ((room != null ? room.sysMes : undefined) === false) {
return;
}
const record = {
t: type,
rid: roomId,
ts: new Date,
msg: message,
u: {
_id: user._id,
username: user.username,
},
groupable: false,
};
if (RocketChat.settings.get('Message_Read_Receipt_Enabled')) {
record.unread = true;
}
_.extend(record, extraData);
record._id = this.insertOrUpsert(record);
return record;
}
createUserJoinWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('uj', roomId, message, user, extraData);
}
createUserLeaveWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('ul', roomId, message, user, extraData);
}
createUserRemovedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('ru', roomId, message, user, extraData);
}
createUserAddedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('au', roomId, message, user, extraData);
}
createCommandWithRoomIdAndUser(command, roomId, user, extraData) {
return this.createWithTypeRoomIdMessageAndUser('command', roomId, command, user, extraData);
}
createUserMutedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('user-muted', roomId, message, user, extraData);
}
createUserUnmutedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('user-unmuted', roomId, message, user, extraData);
}
createNewModeratorWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('new-moderator', roomId, message, user, extraData);
}
createModeratorRemovedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('moderator-removed', roomId, message, user, extraData);
}
createNewOwnerWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('new-owner', roomId, message, user, extraData);
}
createOwnerRemovedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('owner-removed', roomId, message, user, extraData);
}
createNewLeaderWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('new-leader', roomId, message, user, extraData);
}
createLeaderRemovedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('leader-removed', roomId, message, user, extraData);
}
createSubscriptionRoleAddedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('subscription-role-added', roomId, message, user, extraData);
}
createSubscriptionRoleRemovedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('subscription-role-removed', roomId, message, user, extraData);
}
// REMOVE
removeById(_id) {
const query = { _id };
return this.remove(query);
}
removeByRoomId(roomId) {
const query = { rid: roomId };
return this.remove(query);
}
removeByIdPinnedTimestampAndUsers(rid, pinned, ts, users = []) {
const query = {
rid,
ts,
};
if (pinned) {
query.pinned = { $ne: true };
}
if (users.length) {
query['u.username'] = { $in: users };
}
return this.remove(query);
}
removeByIdPinnedTimestampLimitAndUsers(rid, pinned, ts, limit, users = []) {
const query = {
rid,
ts,
};
if (pinned) {
query.pinned = { $ne: true };
}
if (users.length) {
query['u.username'] = { $in: users };
}
const messagesToDelete = RocketChat.models.Messages.find(query, {
fields: {
_id: 1,
},
limit,
}).map(({ _id }) => _id);
return this.remove({
_id: {
$in: messagesToDelete,
},
});
}
removeByUserId(userId) {
const query = { 'u._id': userId };
return this.remove(query);
}
removeFilesByRoomId(roomId) {
this.find({
rid: roomId,
'file._id': {
$exists: true,
},
}, {
fields: {
'file._id': 1,
},
}).fetch().forEach((document) => FileUpload.getStore('Uploads').deleteById(document.file._id));
}
getMessageByFileId(fileID) {
return this.findOne({ 'file._id': fileID });
}
setAsRead(rid, until) {
return this.update({
rid,
unread: true,
ts: { $lt: until },
}, {
$unset: {
unread: 1,
},
}, {
multi: true,
});
}
setAsReadById(_id) {
return this.update({
_id,
}, {
$unset: {
unread: 1,
},
});
}
findUnreadMessagesByRoomAndDate(rid, after) {
const query = {
unread: true,
rid,
};
if (after) {
query.ts = { $gt: after };
}
return this.find(query, {
fields: {
_id: 1,
},
});
}
};