feat: Delete read receipts on messages deletion (#29720)

pull/30135/head^2
Matheus Barbosa Silva 2 years ago committed by GitHub
parent fe6f5cdf88
commit ead7c7bef2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .changeset/tame-pens-occur.md
  2. 3
      apps/meteor/app/federation/server/endpoints/dispatch.js
  3. 16
      apps/meteor/app/lib/server/functions/cleanRoomHistory.ts
  4. 3
      apps/meteor/app/lib/server/functions/deleteMessage.ts
  5. 2
      apps/meteor/app/lib/server/functions/deleteUser.ts
  6. 9
      apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts
  7. 20
      apps/meteor/app/livechat/server/lib/Livechat.js
  8. 2
      apps/meteor/app/livechat/server/lib/LivechatTyped.ts
  9. 8
      apps/meteor/app/message-pin/server/pinMessage.ts
  10. 3
      apps/meteor/app/otr/server/methods/deleteOldOTRMessages.ts
  11. 32
      apps/meteor/app/slackbridge/server/SlackAdapter.js
  12. 3
      apps/meteor/app/threads/server/functions.ts
  13. 13
      apps/meteor/ee/app/message-read-receipt/server/hooks/afterDeleteRoom.ts
  14. 1
      apps/meteor/ee/app/message-read-receipt/server/hooks/index.ts
  15. 6
      apps/meteor/ee/server/lib/message-read-receipt/ReadReceipt.js
  16. 83
      apps/meteor/ee/server/models/raw/ReadReceipts.ts
  17. 2
      apps/meteor/ee/server/models/startup.ts
  18. 3
      apps/meteor/server/lib/moderation/deleteReportedMessages.ts
  19. 75
      apps/meteor/server/models/raw/Messages.ts
  20. 20
      packages/model-typings/src/models/IMessagesModel.ts
  21. 20
      packages/model-typings/src/models/IReadReceiptsModel.ts

@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/model-typings": minor
---
Fixed read receipts not getting deleted after corresponding message is deleted

@ -1,6 +1,6 @@
import { api } from '@rocket.chat/core-services';
import { eventTypes } from '@rocket.chat/core-typings';
import { FederationServers, FederationRoomEvents, Rooms, Messages, Subscriptions, Users } from '@rocket.chat/models';
import { FederationServers, FederationRoomEvents, Rooms, Messages, Subscriptions, Users, ReadReceipts } from '@rocket.chat/models';
import EJSON from 'ejson';
import { API } from '../../../api/server';
@ -325,6 +325,7 @@ const eventHandlers = {
// Remove the message
await Messages.removeById(messageId);
await ReadReceipts.removeByMessageId(messageId);
// Notify the room
void api.broadcast('notify.deleteMessage', roomId, { _id: messageId });

@ -1,6 +1,6 @@
import { api } from '@rocket.chat/core-services';
import type { IRoom } from '@rocket.chat/core-typings';
import { Messages, Rooms, Subscriptions } from '@rocket.chat/models';
import { Messages, Rooms, Subscriptions, ReadReceipts, Users } from '@rocket.chat/models';
import { i18n } from '../../../../server/lib/i18n';
import { FileUpload } from '../../../file-upload/server';
@ -86,6 +86,9 @@ export async function cleanRoomHistory({
}
}
const selectedMessageIds = limit
? await Messages.findByIdPinnedTimestampLimitAndUsers(rid, excludePinned, ignoreDiscussion, ts, limit, fromUsers, ignoreThreads)
: undefined;
const count = await Messages.removeByIdPinnedTimestampLimitAndUsers(
rid,
excludePinned,
@ -94,7 +97,18 @@ export async function cleanRoomHistory({
limit,
fromUsers,
ignoreThreads,
selectedMessageIds,
);
if (!limit) {
const uids = await Users.findByUsernames(fromUsers, { projection: { _id: 1 } })
.map((user) => user._id)
.toArray();
await ReadReceipts.removeByIdPinnedTimestampLimitAndUsers(rid, excludePinned, ignoreDiscussion, ts, uids, ignoreThreads);
} else if (selectedMessageIds) {
await ReadReceipts.removeByMessageIds(selectedMessageIds);
}
if (count) {
const lastMessage = await Messages.getLastVisibleMessageSentWithNoTypeByRoomId(rid);
await Rooms.resetLastMessageById(rid, lastMessage);

@ -1,6 +1,6 @@
import { api } from '@rocket.chat/core-services';
import type { AtLeast, IMessage, IUser } from '@rocket.chat/core-typings';
import { Messages, Rooms, Uploads, Users } from '@rocket.chat/models';
import { Messages, Rooms, Uploads, Users, ReadReceipts } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { Apps } from '../../../../ee/server/apps';
@ -62,6 +62,7 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise<voi
if (!showDeletedStatus) {
await Messages.removeById(message._id);
}
await ReadReceipts.removeByMessageId(message._id);
for await (const file of files) {
file?._id && (await FileUpload.getStore('Uploads').deleteById(file._id));

@ -9,6 +9,7 @@ import {
Rooms,
Subscriptions,
Users,
ReadReceipts,
LivechatUnitMonitors,
ModerationReports,
} from '@rocket.chat/models';
@ -56,6 +57,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
}
await Messages.removeByUserId(userId);
await ReadReceipts.removeByUserId(userId);
await ModerationReports.hideMessageReportsByUserId(
userId,

@ -1,4 +1,4 @@
import { Messages, Roles, Rooms, Subscriptions } from '@rocket.chat/models';
import { Messages, Roles, Rooms, Subscriptions, ReadReceipts } from '@rocket.chat/models';
import { FileUpload } from '../../../file-upload/server';
import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner';
@ -7,7 +7,12 @@ const bulkRoomCleanUp = async (rids: string[]): Promise<unknown> => {
// no bulk deletion for files
await Promise.all(rids.map((rid) => FileUpload.removeFilesByRoomId(rid)));
return Promise.all([Subscriptions.removeByRoomIds(rids), Messages.removeByRoomIds(rids), Rooms.removeByIds(rids)]);
return Promise.all([
Subscriptions.removeByRoomIds(rids),
Messages.removeByRoomIds(rids),
ReadReceipts.removeByRoomIds(rids),
Rooms.removeByIds(rids),
]);
};
export const relinquishRoomOwnerships = async function (

@ -18,6 +18,7 @@ import {
LivechatDepartmentAgents,
Rooms,
Users,
ReadReceipts,
} from '@rocket.chat/models';
import { Match, check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
@ -641,13 +642,18 @@ export const Livechat = {
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {});
const cursor = LivechatRooms.findByVisitorToken(token, extraQuery);
for await (const room of cursor) {
await FileUpload.removeFilesByRoomId(room._id);
await Messages.removeByRoomId(room._id);
}
await Subscriptions.removeByVisitorToken(token);
await LivechatRooms.removeByVisitorToken(token);
await LivechatInquiry.removeByVisitorToken(token);
await Promise.all([
FileUpload.removeFilesByRoomId(room._id),
Messages.removeByRoomId(room._id),
ReadReceipts.removeByRoomId(room._id),
]);
}
await Promise.all([
Subscriptions.removeByVisitorToken(token),
LivechatRooms.removeByVisitorToken(token),
LivechatInquiry.removeByVisitorToken(token),
]);
},
async saveDepartmentAgents(_id, departmentAgents) {

@ -21,6 +21,7 @@ import {
Messages,
Users,
LivechatDepartmentAgents,
ReadReceipts,
} from '@rocket.chat/models';
import { Random } from '@rocket.chat/random';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
@ -401,6 +402,7 @@ class LivechatClass {
const result = await Promise.allSettled([
Messages.removeByRoomId(rid),
ReadReceipts.removeByRoomId(rid),
Subscriptions.removeByRoomId(rid),
LivechatInquiry.removeByRoomId(rid),
LivechatRooms.removeById(rid),

@ -1,7 +1,7 @@
import { Message } from '@rocket.chat/core-services';
import { isQuoteAttachment } from '@rocket.chat/core-typings';
import type { IMessage, IUser, MessageAttachment, MessageQuoteAttachment } from '@rocket.chat/core-typings';
import { Messages, Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { Messages, Rooms, Subscriptions, Users, ReadReceipts } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import { check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
@ -113,6 +113,9 @@ Meteor.methods<ServerMethods>({
originalMessage = await callbacks.run('beforeSaveMessage', originalMessage);
await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned);
if (settings.get('Message_Read_Receipt_Store_Users')) {
await ReadReceipts.setPinnedByMessageId(message._id, originalMessage.pinned);
}
if (isTheLastMessage(room, message)) {
await Rooms.setLastMessagePinned(room._id, originalMessage.pinnedBy, originalMessage.pinned);
}
@ -219,6 +222,9 @@ Meteor.methods<ServerMethods>({
await Apps.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned);
await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned);
if (settings.get('Message_Read_Receipt_Store_Users')) {
await ReadReceipts.setPinnedByMessageId(originalMessage._id, originalMessage.pinned);
}
return true;
},

@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Messages, Subscriptions } from '@rocket.chat/models';
import { Messages, Subscriptions, ReadReceipts } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
@ -28,5 +28,6 @@ Meteor.methods<ServerMethods>({
}
await Messages.deleteOldOTRMessages(roomId, now);
await ReadReceipts.removeOTRReceiptsUntilDate(roomId, now);
},
});

@ -3,7 +3,7 @@ import https from 'https';
import url from 'url';
import { Message } from '@rocket.chat/core-services';
import { Messages, Rooms, Users } from '@rocket.chat/models';
import { Messages, Rooms, Users, ReadReceipts } from '@rocket.chat/models';
import { RTMClient } from '@slack/rtm-api';
import { Meteor } from 'meteor/meteor';
@ -670,6 +670,10 @@ export default class SlackAdapter {
});
}
createSlackMessageId(ts, channelId) {
return `slack${channelId ? `-${channelId}` : ''}-${ts.replace(/\./g, '-')}`;
}
async postMessage(slackChannel, rocketMessage) {
if (slackChannel && slackChannel.id) {
let iconUrl = getUserAvatarURL(rocketMessage.u && rocketMessage.u.username);
@ -975,7 +979,7 @@ export default class SlackAdapter {
async processShareMessage(rocketChannel, rocketUser, slackMessage, isImporting) {
if (slackMessage.file && slackMessage.file.url_private_download !== undefined) {
const details = {
message_id: `slack-${slackMessage.ts.replace(/\./g, '-')}`,
message_id: this.createSlackMessageId(slackMessage.ts),
name: slackMessage.file.name,
size: slackMessage.file.size,
type: slackMessage.file.mimetype,
@ -1012,13 +1016,12 @@ export default class SlackAdapter {
],
};
if (!isImporting) {
await Messages.setPinnedByIdAndUserId(
`slack-${slackMessage.attachments[0].channel_id}-${slackMessage.attachments[0].ts.replace(/\./g, '-')}`,
rocketMsgObj.u,
true,
new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000),
);
if (!isImporting && slackMessage.attachments[0].channel_id && slackMessage.attachments[0].ts) {
const messageId = this.createSlackMessageId(slackMessage.attachments[0].ts, slackMessage.attachments[0].channel_id);
await Messages.setPinnedByIdAndUserId(messageId, rocketMsgObj.u, true, new Date(parseInt(slackMessage.ts.split('.')[0]) * 1000));
if (settings.get('Message_Read_Receipt_Store_Users')) {
await ReadReceipts.setPinnedByMessageId(messageId, true);
}
}
return rocketMsgObj;
@ -1224,12 +1227,11 @@ export default class SlackAdapter {
],
};
await Messages.setPinnedByIdAndUserId(
`slack-${pin.channel}-${pin.message.ts.replace(/\./g, '-')}`,
msgObj.u,
true,
new Date(parseInt(pin.message.ts.split('.')[0]) * 1000),
);
const messageId = this.createSlackMessageId(pin.message.ts, pin.channel);
await Messages.setPinnedByIdAndUserId(messageId, msgObj.u, true, new Date(parseInt(pin.message.ts.split('.')[0]) * 1000));
if (settings.get('Message_Read_Receipt_Store_Users')) {
await ReadReceipts.setPinnedByMessageId(messageId, true);
}
}
}
}

@ -1,6 +1,6 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { isEditedMessage } from '@rocket.chat/core-typings';
import { Messages, Subscriptions } from '@rocket.chat/models';
import { Messages, Subscriptions, ReadReceipts } from '@rocket.chat/models';
import { getMentions } from '../../lib/server/lib/notifyUsersOnMessage';
@ -21,6 +21,7 @@ export async function reply({ tmid }: { tmid?: string }, message: IMessage, pare
];
await Messages.updateRepliesByThreadId(tmid, addToReplies, ts);
await ReadReceipts.setAsThreadById(tmid);
const replies = await Messages.getThreadFollowsByThreadId(tmid);

@ -0,0 +1,13 @@
import { ReadReceipts } from '@rocket.chat/models';
import { callbacks } from '../../../../../lib/callbacks';
callbacks.add(
'afterDeleteRoom',
async (rid) => {
await ReadReceipts.removeByRoomId(rid);
return rid;
},
callbacks.priority.LOW,
'DeleteReadReceipts',
);

@ -1,2 +1,3 @@
import './afterReadMessages';
import './afterSaveMessage';
import './afterDeleteRoom';

@ -65,7 +65,7 @@ export const ReadReceipt = {
}
const extraData = roomCoordinator.getRoomDirectives(t).getReadReceiptsExtraData(message);
this.storeReadReceipts([{ _id: message._id }], roomId, userId, extraData);
this.storeReadReceipts([message], roomId, userId, extraData);
},
async storeThreadMessagesReadReceipts(tmid, userId, userLastSeen) {
@ -92,6 +92,10 @@ export const ReadReceipt = {
userId,
messageId: message._id,
ts,
...(message.t && { t: message.t }),
...(message.pinned && { pinned: true }),
...(message.drid && { drid: message.drid }),
...(message.tmid && { tmid: message.tmid }),
...extraData,
}));

@ -1,7 +1,8 @@
import type { ReadReceipt, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IUser, IMessage, ReadReceipt, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IReadReceiptsModel } from '@rocket.chat/model-typings';
import type { Collection, FindCursor, Db, IndexDescription } from 'mongodb';
import type { Collection, FindCursor, Db, IndexDescription, DeleteResult, Filter, UpdateResult, Document } from 'mongodb';
import { otrSystemMessages } from '../../../../app/otr/lib/constants';
import { BaseRaw } from '../../../../server/models/raw/BaseRaw';
export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceiptsModel {
@ -10,10 +11,86 @@ export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceip
}
protected modelIndexes(): IndexDescription[] {
return [{ key: { roomId: 1, userId: 1, messageId: 1 }, unique: true }, { key: { messageId: 1 } }];
return [{ key: { roomId: 1, userId: 1, messageId: 1 }, unique: true }, { key: { messageId: 1 } }, { key: { userId: 1 } }];
}
findByMessageId(messageId: string): FindCursor<ReadReceipt> {
return this.find({ messageId });
}
removeByUserId(userId: string): Promise<DeleteResult> {
return this.deleteMany({ userId });
}
removeByRoomId(roomId: string): Promise<DeleteResult> {
return this.deleteMany({ roomId });
}
removeByRoomIds(roomIds: string[]): Promise<DeleteResult> {
return this.deleteMany({ roomId: { $in: roomIds } });
}
removeByMessageId(messageId: string): Promise<DeleteResult> {
return this.deleteMany({ messageId });
}
removeByMessageIds(messageIds: string[]): Promise<DeleteResult> {
return this.deleteMany({ messageId: { $in: messageIds } });
}
removeOTRReceiptsUntilDate(roomId: string, until: Date): Promise<DeleteResult> {
const query = {
roomId,
t: {
$in: [
'otr',
otrSystemMessages.USER_JOINED_OTR,
otrSystemMessages.USER_REQUESTED_OTR_KEY_REFRESH,
otrSystemMessages.USER_KEY_REFRESHED_SUCCESSFULLY,
],
},
ts: { $lte: until },
};
return this.deleteMany(query);
}
async removeByIdPinnedTimestampLimitAndUsers(
roomId: string,
ignorePinned: boolean,
ignoreDiscussion: boolean,
ts: Filter<IMessage>['ts'],
users: IUser['_id'][],
ignoreThreads: boolean,
): Promise<DeleteResult> {
const query: Filter<ReadReceipt> = {
roomId,
ts,
};
if (ignorePinned) {
query.pinned = { $ne: true };
}
if (ignoreDiscussion) {
query.drid = { $exists: false };
}
if (ignoreThreads) {
query.tmid = { $exists: false };
}
if (users.length) {
query.userId = { $in: users };
}
return this.deleteMany(query);
}
setPinnedByMessageId(messageId: string, pinned = true): Promise<Document | UpdateResult> {
return this.updateMany({ messageId }, { $set: { pinned } });
}
setAsThreadById(messageId: string): Promise<Document | UpdateResult> {
return this.updateMany({ messageId }, { $set: { tmid: messageId } });
}
}

@ -6,6 +6,7 @@ import { onLicense } from '../../app/license/server/license';
import('./LivechatPriority');
import('./OmnichannelServiceLevelAgreements');
import('./AuditLog');
import('./ReadReceipts');
await onLicense('livechat-enterprise', () => {
import('./CannedResponse');
@ -14,7 +15,6 @@ await onLicense('livechat-enterprise', () => {
import('./LivechatUnitMonitors');
import('./LivechatRooms');
import('./LivechatInquiry');
import('./ReadReceipts');
import('./LivechatDepartment');
import('./Users');
import('./LivechatDepartmentAgents');

@ -1,5 +1,5 @@
import type { IUser, IMessage } from '@rocket.chat/core-typings';
import { Messages, Uploads } from '@rocket.chat/models';
import { Messages, Uploads, ReadReceipts } from '@rocket.chat/models';
import { FileUpload } from '../../../app/file-upload/server';
import { settings } from '../../../app/settings/server';
@ -39,6 +39,7 @@ export async function deleteReportedMessages(messages: IMessage[], user: IUser):
if (!showDeletedStatus) {
await Messages.deleteMany({ _id: { $in: messageIds } });
}
await ReadReceipts.removeByMessageIds(messageIds);
const store = FileUpload.getStore('Uploads');
await Promise.all(files.map((file) => store.deleteById(file)));

@ -1348,14 +1348,53 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
return this.find(query, options);
}
async findByIdPinnedTimestampLimitAndUsers(
rid: string,
ignorePinned: boolean,
ignoreDiscussion = true,
ts: Filter<IMessage>['ts'],
limit: number,
users: string[] = [],
ignoreThreads = true,
): Promise<string[]> {
const query: Filter<IMessage> = {
rid,
ts,
...(users.length > 0 && { 'u.username': { $in: users } }),
};
if (ignorePinned) {
query.pinned = { $ne: true };
}
if (ignoreDiscussion) {
query.drid = { $exists: false };
}
if (ignoreThreads) {
query.tmid = { $exists: false };
query.tcount = { $exists: false };
}
return (
await this.find(query, {
projection: {
_id: 1,
},
limit,
}).toArray()
).map(({ _id }) => _id);
}
async removeByIdPinnedTimestampLimitAndUsers(
rid: string,
pinned: boolean,
ignorePinned: boolean,
ignoreDiscussion = true,
ts: Filter<IMessage>['ts'],
limit: number,
users: string[] = [],
ignoreThreads = true,
selectedMessageIds: string[] = [],
): Promise<number> {
const query: Filter<IMessage> = {
rid,
@ -1363,7 +1402,7 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
...(users.length > 0 && { 'u.username': { $in: users } }),
};
if (pinned) {
if (ignorePinned) {
query.pinned = { $ne: true };
}
@ -1393,7 +1432,6 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
if (!limit) {
const count = (await this.deleteMany(query)).deletedCount - notCountedMessages;
if (count) {
// decrease message count
await Rooms.decreaseMessageCountById(rid, count);
@ -1402,24 +1440,11 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
return count;
}
const messagesToDelete = await this.find(query, {
projection: {
_id: 1,
_hidden: 1,
t: 1,
editedAt: 1,
editedBy: 1,
},
limit,
}).toArray();
const messagesIdsToDelete = messagesToDelete.map(({ _id }) => _id);
const count =
(
await this.deleteMany({
_id: {
$in: messagesIdsToDelete,
$in: selectedMessageIds,
},
})
).deletedCount - notCountedMessages;
@ -1511,7 +1536,7 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
);
}
findVisibleUnreadMessagesByRoomAndDate(rid: string, after: Date): FindCursor<Pick<IMessage, '_id'>> {
findVisibleUnreadMessagesByRoomAndDate(rid: string, after: Date): FindCursor<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>> {
const query = {
unread: true,
rid,
@ -1529,11 +1554,19 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
return this.find(query, {
projection: {
_id: 1,
t: 1,
pinned: 1,
drid: 1,
tmid: 1,
},
});
}
findUnreadThreadMessagesByDate(tmid: string, userId: string, after: Date): FindCursor<Pick<IMessage, '_id'>> {
findUnreadThreadMessagesByDate(
tmid: string,
userId: string,
after: Date,
): FindCursor<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>> {
const query = {
'u._id': { $ne: userId },
'unread': true,
@ -1545,6 +1578,10 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
return this.find(query, {
projection: {
_id: 1,
t: 1,
pinned: 1,
drid: 1,
tmid: 1,
},
});
}

@ -228,13 +228,23 @@ export interface IMessagesModel extends IBaseModel<IMessage> {
removeByIdPinnedTimestampLimitAndUsers(
rid: string,
pinned: boolean,
ignorePinned: boolean,
ignoreDiscussion: boolean,
ts: Filter<IMessage>['ts'],
limit: number,
users: string[],
ignoreThreads: boolean,
selectedMessageIds?: string[],
): Promise<number>;
findByIdPinnedTimestampLimitAndUsers(
rid: string,
ignorePinned: boolean,
ignoreDiscussion: boolean,
ts: Filter<IMessage>['ts'],
limit: number,
users: string[],
ignoreThreads: boolean,
): Promise<string[]>;
removeByUserId(userId: string): Promise<DeleteResult>;
getThreadFollowsByThreadId(tmid: string): Promise<string[] | undefined>;
setVisibleMessagesAsRead(rid: string, until: Date): Promise<UpdateResult | Document>;
@ -243,8 +253,12 @@ export interface IMessagesModel extends IBaseModel<IMessage> {
setThreadMessagesAsRead(tmid: string, until: Date): Promise<UpdateResult | Document>;
updateRepliesByThreadId(tmid: string, replies: string[], ts: Date): Promise<UpdateResult>;
refreshDiscussionMetadata(room: Pick<IRoom, '_id' | 'msgs' | 'lm'>): Promise<UpdateResult | Document | false>;
findUnreadThreadMessagesByDate(tmid: string, userId: string, after: Date): FindCursor<Pick<IMessage, '_id'>>;
findVisibleUnreadMessagesByRoomAndDate(rid: string, after: Date): FindCursor<Pick<IMessage, '_id'>>;
findUnreadThreadMessagesByDate(
tmid: string,
userId: string,
after: Date,
): FindCursor<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>>;
findVisibleUnreadMessagesByRoomAndDate(rid: string, after: Date): FindCursor<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>>;
setAsReadById(_id: string): Promise<UpdateResult>;
countThreads(): Promise<number>;
addThreadFollowerByThreadId(tmid: string, userId: string): Promise<UpdateResult>;

@ -1,8 +1,24 @@
import type { ReadReceipt } from '@rocket.chat/core-typings';
import type { FindCursor } from 'mongodb';
import type { ReadReceipt, IUser, IMessage } from '@rocket.chat/core-typings';
import type { FindCursor, DeleteResult, UpdateResult, Document, Filter } from 'mongodb';
import type { IBaseModel } from './IBaseModel';
export interface IReadReceiptsModel extends IBaseModel<ReadReceipt> {
findByMessageId(messageId: string): FindCursor<ReadReceipt>;
removeByUserId(userId: string): Promise<DeleteResult>;
removeByRoomId(roomId: string): Promise<DeleteResult>;
removeByRoomIds(roomIds: string[]): Promise<DeleteResult>;
removeByMessageId(messageId: string): Promise<DeleteResult>;
removeByMessageIds(messageIds: string[]): Promise<DeleteResult>;
removeOTRReceiptsUntilDate(roomId: string, until: Date): Promise<DeleteResult>;
removeByIdPinnedTimestampLimitAndUsers(
roomId: string,
ignorePinned: boolean,
ignoreDiscussion: boolean,
ts: Filter<IMessage>['ts'],
users: IUser['_id'][],
ignoreThreads: boolean,
): Promise<DeleteResult>;
setPinnedByMessageId(messageId: string, pinned?: boolean): Promise<Document | UpdateResult>;
setAsThreadById(messageId: string): Promise<Document | UpdateResult>;
}

Loading…
Cancel
Save