mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
chore: prevent read receipts from loading unnecessary data when not saving detailed info (#37280)
This commit is contained in:
parent
a86fbe845e
commit
2b2aa3e5d0
@ -1,4 +1,4 @@
|
||||
import type { ReadReceipt } from '@rocket.chat/core-typings';
|
||||
import type { IReadReceiptWithUser } from '@rocket.chat/core-typings';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { UserAvatar } from '@rocket.chat/ui-avatar';
|
||||
import { useUserDisplayName } from '@rocket.chat/ui-client';
|
||||
@ -6,7 +6,7 @@ import type { ReactElement } from 'react';
|
||||
|
||||
import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime';
|
||||
|
||||
const ReadReceiptRow = ({ user, ts }: ReadReceipt): ReactElement => {
|
||||
const ReadReceiptRow = ({ user, ts }: IReadReceiptWithUser): ReactElement => {
|
||||
const displayName = useUserDisplayName(user || {});
|
||||
const formatDateAndTime = useFormatDateAndTime({ withSeconds: true });
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import type {
|
||||
RoomType,
|
||||
IUser,
|
||||
IMessage,
|
||||
ReadReceipt,
|
||||
IReadReceipt,
|
||||
ValueOf,
|
||||
AtLeast,
|
||||
ISubscription,
|
||||
@ -106,7 +106,7 @@ export interface IRoomTypeServerDirectives {
|
||||
) => Promise<{ title: string | undefined; text: string; name: string | undefined }>;
|
||||
getMsgSender: (message: IMessage) => Promise<IUser | null>;
|
||||
includeInRoomSearch: () => boolean;
|
||||
getReadReceiptsExtraData: (message: IMessage) => Partial<ReadReceipt>;
|
||||
getReadReceiptsExtraData: (message: IMessage) => Partial<IReadReceipt>;
|
||||
includeInDashboard: () => boolean;
|
||||
roomFind?: (rid: string) => Promise<IRoom | undefined> | Promise<IOmnichannelRoom | null> | IRoom | undefined;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { IMessage, ReadReceipt } from '@rocket.chat/core-typings';
|
||||
import type { IMessage, IReadReceiptWithUser } from '@rocket.chat/core-typings';
|
||||
import { License } from '@rocket.chat/license';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
@ -14,7 +14,7 @@ declare module '@rocket.chat/rest-typings' {
|
||||
interface Endpoints {
|
||||
'/v1/chat.getMessageReadReceipts': {
|
||||
GET: (params: GetMessageReadReceiptsProps) => {
|
||||
receipts: ReadReceipt[];
|
||||
receipts: IReadReceiptWithUser[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { api } from '@rocket.chat/core-services';
|
||||
import type { IMessage, IRoom, IReadReceipt, IReadReceiptWithUser } from '@rocket.chat/core-typings';
|
||||
import { LivechatVisitors, ReadReceipts, Messages, Rooms, Subscriptions, Users } from '@rocket.chat/models';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
|
||||
@ -8,21 +9,21 @@ import { SystemLogger } from '../../../../server/lib/logger/system';
|
||||
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
|
||||
|
||||
// debounced function by roomId, so multiple calls within 2 seconds to same roomId runs only once
|
||||
const list = {};
|
||||
const debounceByRoomId = function (fn) {
|
||||
return function (roomId, ...args) {
|
||||
clearTimeout(list[roomId]);
|
||||
list[roomId] = setTimeout(() => {
|
||||
fn.call(this, roomId, ...args);
|
||||
delete list[roomId];
|
||||
const list: Record<string, NodeJS.Timeout> = {};
|
||||
const debounceByRoomId = function (fn: (room: IRoom) => Promise<void>) {
|
||||
return function (this: unknown, room: IRoom) {
|
||||
clearTimeout(list[room._id]);
|
||||
list[room._id] = setTimeout(() => {
|
||||
void fn.call(this, room);
|
||||
delete list[room._id];
|
||||
}, 2000);
|
||||
};
|
||||
};
|
||||
|
||||
const updateMessages = debounceByRoomId(async ({ _id, lm }) => {
|
||||
const updateMessages = debounceByRoomId(async ({ _id, lm }: IRoom) => {
|
||||
// @TODO maybe store firstSubscription in room object so we don't need to call the above update method
|
||||
const firstSubscription = await Subscriptions.getMinimumLastSeenByRoomId(_id);
|
||||
if (!firstSubscription || !firstSubscription.ls) {
|
||||
if (!firstSubscription?.ls) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -31,14 +32,14 @@ const updateMessages = debounceByRoomId(async ({ _id, lm }) => {
|
||||
void api.broadcast('notify.messagesRead', { rid: _id, until: firstSubscription.ls });
|
||||
}
|
||||
|
||||
if (lm <= firstSubscription.ls) {
|
||||
if (lm && lm <= firstSubscription.ls) {
|
||||
await Rooms.setLastMessageAsRead(_id);
|
||||
void notifyOnRoomChangedById(_id);
|
||||
}
|
||||
});
|
||||
|
||||
export const ReadReceipt = {
|
||||
async markMessagesAsRead(roomId, userId, userLastSeen) {
|
||||
class ReadReceiptClass {
|
||||
async markMessagesAsRead(roomId: string, userId: string, userLastSeen: Date) {
|
||||
if (!settings.get('Message_Read_Receipt_Enabled')) {
|
||||
return;
|
||||
}
|
||||
@ -46,16 +47,22 @@ export const ReadReceipt = {
|
||||
const room = await Rooms.findOneById(roomId, { projection: { lm: 1 } });
|
||||
|
||||
// if users last seen is greater than room's last message, it means the user already have this room marked as read
|
||||
if (!room || userLastSeen > room.lm) {
|
||||
if (!room || (room.lm && userLastSeen > room.lm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.storeReadReceipts(await Messages.findVisibleUnreadMessagesByRoomAndDate(roomId, userLastSeen).toArray(), roomId, userId);
|
||||
void this.storeReadReceipts(
|
||||
() => {
|
||||
return Messages.findVisibleUnreadMessagesByRoomAndDate(roomId, userLastSeen).toArray();
|
||||
},
|
||||
roomId,
|
||||
userId,
|
||||
);
|
||||
|
||||
await updateMessages(room);
|
||||
},
|
||||
updateMessages(room);
|
||||
}
|
||||
|
||||
async markMessageAsReadBySender(message, { _id: roomId, t }, userId) {
|
||||
async markMessageAsReadBySender(message: IMessage, { _id: roomId, t }: { _id: string; t: string }, userId: string) {
|
||||
if (!settings.get('Message_Read_Receipt_Enabled')) {
|
||||
return;
|
||||
}
|
||||
@ -76,10 +83,17 @@ export const ReadReceipt = {
|
||||
}
|
||||
|
||||
const extraData = roomCoordinator.getRoomDirectives(t).getReadReceiptsExtraData(message);
|
||||
this.storeReadReceipts([message], roomId, userId, extraData);
|
||||
},
|
||||
void this.storeReadReceipts(
|
||||
() => {
|
||||
return Promise.resolve([message]);
|
||||
},
|
||||
roomId,
|
||||
userId,
|
||||
extraData,
|
||||
);
|
||||
}
|
||||
|
||||
async storeThreadMessagesReadReceipts(tmid, userId, userLastSeen) {
|
||||
async storeThreadMessagesReadReceipts(tmid: string, userId: string, userLastSeen: Date) {
|
||||
if (!settings.get('Message_Read_Receipt_Enabled')) {
|
||||
return;
|
||||
}
|
||||
@ -87,17 +101,28 @@ export const ReadReceipt = {
|
||||
const message = await Messages.findOneById(tmid, { projection: { tlm: 1, rid: 1 } });
|
||||
|
||||
// if users last seen is greater than thread's last message, it means the user has already marked this thread as read
|
||||
if (!message || userLastSeen > message.tlm) {
|
||||
if (!message || (message.tlm && userLastSeen > message.tlm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.storeReadReceipts(await Messages.findUnreadThreadMessagesByDate(tmid, userId, userLastSeen).toArray(), message.rid, userId);
|
||||
},
|
||||
void this.storeReadReceipts(
|
||||
() => {
|
||||
return Messages.findUnreadThreadMessagesByDate(message.rid, tmid, userId, userLastSeen).toArray();
|
||||
},
|
||||
message.rid,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
async storeReadReceipts(messages, roomId, userId, extraData = {}) {
|
||||
private async storeReadReceipts(
|
||||
getMessages: () => Promise<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>[]>,
|
||||
roomId: string,
|
||||
userId: string,
|
||||
extraData: Partial<IReadReceipt> = {},
|
||||
) {
|
||||
if (settings.get('Message_Read_Receipt_Store_Users')) {
|
||||
const ts = new Date();
|
||||
const receipts = messages.map((message) => ({
|
||||
const receipts = (await getMessages()).map((message) => ({
|
||||
_id: Random.id(),
|
||||
roomId,
|
||||
userId,
|
||||
@ -120,18 +145,20 @@ export const ReadReceipt = {
|
||||
SystemLogger.error({ msg: 'Error inserting read receipts per user', err });
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
async getReceipts(message) {
|
||||
async getReceipts(message: Pick<IMessage, '_id'>): Promise<IReadReceiptWithUser[]> {
|
||||
const receipts = await ReadReceipts.findByMessageId(message._id).toArray();
|
||||
|
||||
return Promise.all(
|
||||
receipts.map(async (receipt) => ({
|
||||
...receipt,
|
||||
user: receipt.token
|
||||
user: (receipt.token
|
||||
? await LivechatVisitors.getVisitorByToken(receipt.token, { projection: { username: 1, name: 1 } })
|
||||
: await Users.findOneById(receipt.userId, { projection: { username: 1, name: 1 } }),
|
||||
: await Users.findOneById(receipt.userId, { projection: { username: 1, name: 1, token: 1 } })) as IReadReceiptWithUser['user'],
|
||||
})),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const ReadReceipt = new ReadReceiptClass();
|
||||
@ -42,7 +42,7 @@ export class MessageReadsService extends ServiceClassInternal implements IMessag
|
||||
|
||||
const firstRead = await MessageReads.getMinimumLastSeenByThreadId(tmid);
|
||||
if (firstRead?.ls) {
|
||||
const result = await Messages.setThreadMessagesAsRead(tmid, firstRead.ls);
|
||||
const result = await Messages.setThreadMessagesAsRead(threadMessage.rid, tmid, firstRead.ls);
|
||||
if (result.modifiedCount > 0) {
|
||||
void api.broadcast('notify.messagesRead', { rid: threadMessage.rid, tmid, until: firstRead.ls });
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ReadReceipt as ReadReceiptType, IMessage } from '@rocket.chat/core-typings';
|
||||
import type { IReadReceiptWithUser, IMessage } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { License } from '@rocket.chat/license';
|
||||
import { Messages } from '@rocket.chat/models';
|
||||
@ -11,11 +11,11 @@ import { ReadReceipt } from '../lib/message-read-receipt/ReadReceipt';
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
getReadReceipts(options: { messageId: IMessage['_id'] }): ReadReceiptType[];
|
||||
getReadReceipts(options: { messageId: IMessage['_id'] }): IReadReceiptWithUser[];
|
||||
}
|
||||
}
|
||||
|
||||
export const getReadReceiptsFunction = async function (messageId: IMessage['_id'], userId: string): Promise<ReadReceiptType[]> {
|
||||
export const getReadReceiptsFunction = async function (messageId: IMessage['_id'], userId: string): Promise<IReadReceiptWithUser[]> {
|
||||
if (!License.hasModule('message-read-receipt')) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature', { method: 'getReadReceipts' });
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import type { IUser, IMessage, ReadReceipt, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
|
||||
import type { IUser, IMessage, IReadReceipt, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
|
||||
import type { IReadReceiptsModel } from '@rocket.chat/model-typings';
|
||||
import { BaseRaw } from '@rocket.chat/models';
|
||||
import type { Collection, FindCursor, Db, IndexDescription, DeleteResult, Filter, UpdateResult, Document } from 'mongodb';
|
||||
|
||||
import { otrSystemMessages } from '../../../../app/otr/lib/constants';
|
||||
|
||||
export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceiptsModel {
|
||||
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<ReadReceipt>>) {
|
||||
export class ReadReceiptsRaw extends BaseRaw<IReadReceipt> implements IReadReceiptsModel {
|
||||
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<IReadReceipt>>) {
|
||||
super(db, 'read_receipts', trash);
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceip
|
||||
return [{ key: { roomId: 1, userId: 1, messageId: 1 }, unique: true }, { key: { messageId: 1 } }, { key: { userId: 1 } }];
|
||||
}
|
||||
|
||||
findByMessageId(messageId: string): FindCursor<ReadReceipt> {
|
||||
findByMessageId(messageId: string): FindCursor<IReadReceipt> {
|
||||
return this.find({ messageId });
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceip
|
||||
}
|
||||
|
||||
removeOTRReceiptsUntilDate(roomId: string, until: Date): Promise<DeleteResult> {
|
||||
const query = {
|
||||
return this.col.deleteMany({
|
||||
roomId,
|
||||
t: {
|
||||
$in: [
|
||||
@ -50,8 +50,7 @@ export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceip
|
||||
],
|
||||
},
|
||||
ts: { $lte: until },
|
||||
};
|
||||
return this.col.deleteMany(query);
|
||||
});
|
||||
}
|
||||
|
||||
async removeByIdPinnedTimestampLimitAndUsers(
|
||||
@ -62,7 +61,7 @@ export class ReadReceiptsRaw extends BaseRaw<ReadReceipt> implements IReadReceip
|
||||
users: IUser['_id'][],
|
||||
ignoreThreads: boolean,
|
||||
): Promise<DeleteResult> {
|
||||
const query: Filter<ReadReceipt> = {
|
||||
const query: Filter<IReadReceipt> = {
|
||||
roomId,
|
||||
ts,
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { getUserDisplayName } from '@rocket.chat/core-typings';
|
||||
import type { IRoom, RoomType, IUser, IMessage, ReadReceipt, ValueOf, AtLeast } from '@rocket.chat/core-typings';
|
||||
import type { IRoom, RoomType, IUser, IMessage, IReadReceipt, ValueOf, AtLeast } from '@rocket.chat/core-typings';
|
||||
import { Users } from '@rocket.chat/models';
|
||||
|
||||
import { settings } from '../../../app/settings/server';
|
||||
@ -56,7 +56,7 @@ class RoomCoordinatorServer extends RoomCoordinator {
|
||||
includeInRoomSearch(): boolean {
|
||||
return false;
|
||||
},
|
||||
getReadReceiptsExtraData(_message: IMessage): Partial<ReadReceipt> {
|
||||
getReadReceiptsExtraData(_message: IMessage): Partial<IReadReceipt> {
|
||||
return {};
|
||||
},
|
||||
includeInDashboard(): boolean {
|
||||
|
||||
20
packages/core-typings/src/IReadReceipt.ts
Normal file
20
packages/core-typings/src/IReadReceipt.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { IMessage } from './IMessage/IMessage';
|
||||
import type { IRoom } from './IRoom';
|
||||
import type { IUser } from './IUser';
|
||||
|
||||
export type IReadReceipt = {
|
||||
token?: string;
|
||||
messageId: IMessage['_id'];
|
||||
roomId: IRoom['_id'];
|
||||
ts: Date;
|
||||
t?: IMessage['t'];
|
||||
pinned?: IMessage['pinned'];
|
||||
drid?: IMessage['drid'];
|
||||
tmid?: IMessage['tmid'];
|
||||
userId: IUser['_id'];
|
||||
_id: string;
|
||||
};
|
||||
|
||||
export type IReadReceiptWithUser = IReadReceipt & {
|
||||
user?: Pick<IUser, '_id' | 'name' | 'username'> | undefined;
|
||||
};
|
||||
@ -1,13 +0,0 @@
|
||||
import type { ILivechatVisitor } from './ILivechatVisitor';
|
||||
import type { IMessage } from './IMessage/IMessage';
|
||||
import type { IRoom } from './IRoom';
|
||||
import type { IUser } from './IUser';
|
||||
|
||||
export type ReadReceipt = {
|
||||
messageId: IMessage['_id'];
|
||||
roomId: IRoom['_id'];
|
||||
ts: Date;
|
||||
user: Pick<IUser, '_id' | 'name' | 'username'> | ILivechatVisitor | null;
|
||||
userId: IUser['_id'];
|
||||
_id: string;
|
||||
};
|
||||
@ -65,7 +65,7 @@ export * from './IAvatar';
|
||||
export * from './ICustomUserStatus';
|
||||
export * from './IEmailMessageHistory';
|
||||
|
||||
export * from './ReadReceipt';
|
||||
export * from './IReadReceipt';
|
||||
export * from './MessageReads';
|
||||
export * from './IUpload';
|
||||
export * from './IOEmbedCache';
|
||||
|
||||
@ -274,10 +274,11 @@ export interface IMessagesModel extends IBaseModel<IMessage> {
|
||||
setVisibleMessagesAsRead(rid: string, until: Date): Promise<UpdateResult | Document>;
|
||||
getMessageByFileIdAndUsername(fileID: string, userId: string): Promise<IMessage | null>;
|
||||
getMessageByFileId(fileID: string): Promise<IMessage | null>;
|
||||
setThreadMessagesAsRead(tmid: string, until: Date): Promise<UpdateResult | Document>;
|
||||
setThreadMessagesAsRead(rid: string, tmid: string, until: Date): Promise<UpdateResult | Document>;
|
||||
updateRepliesByThreadId(tmid: string, replies: string[], ts: Date): Promise<UpdateResult>;
|
||||
refreshDiscussionMetadata(room: Pick<IRoom, '_id' | 'msgs' | 'lm'>): Promise<null | WithId<IMessage>>;
|
||||
findUnreadThreadMessagesByDate(
|
||||
rid: string,
|
||||
tmid: string,
|
||||
userId: string,
|
||||
after: Date,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type { ReadReceipt, IUser, IMessage } from '@rocket.chat/core-typings';
|
||||
import type { IReadReceipt, 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>;
|
||||
export interface IReadReceiptsModel extends IBaseModel<IReadReceipt> {
|
||||
findByMessageId(messageId: string): FindCursor<IReadReceipt>;
|
||||
removeByUserId(userId: string): Promise<DeleteResult>;
|
||||
removeByRoomId(roomId: string): Promise<DeleteResult>;
|
||||
removeByRoomIds(roomIds: string[]): Promise<DeleteResult>;
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { IUser, IMessage, ReadReceipt } from '@rocket.chat/core-typings';
|
||||
import type { IUser, IMessage, IReadReceipt } from '@rocket.chat/core-typings';
|
||||
import type { IReadReceiptsModel } from '@rocket.chat/model-typings';
|
||||
import type { FindCursor, DeleteResult, Filter, UpdateResult, Document } from 'mongodb';
|
||||
|
||||
import { BaseDummy } from './BaseDummy';
|
||||
|
||||
export class ReadReceiptsDummy extends BaseDummy<ReadReceipt> implements IReadReceiptsModel {
|
||||
export class ReadReceiptsDummy extends BaseDummy<IReadReceipt> implements IReadReceiptsModel {
|
||||
constructor() {
|
||||
super('read_receipts');
|
||||
}
|
||||
|
||||
findByMessageId(_messageId: string): FindCursor<ReadReceipt> {
|
||||
findByMessageId(_messageId: string): FindCursor<IReadReceipt> {
|
||||
return this.find({});
|
||||
}
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
|
||||
{ key: { location: '2dsphere' } },
|
||||
{ key: { slackTs: 1, slackBotId: 1 }, sparse: true },
|
||||
{ key: { unread: 1 }, sparse: true },
|
||||
{ key: { rid: 1, unread: 1, ts: 1, tmid: 1, tshow: 1 }, partialFilterExpression: { unread: { $exists: true } } },
|
||||
{ key: { 'pinnedBy._id': 1 }, sparse: true },
|
||||
{ key: { 'starred._id': 1 }, sparse: true },
|
||||
|
||||
@ -1569,12 +1570,13 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
|
||||
);
|
||||
}
|
||||
|
||||
setThreadMessagesAsRead(tmid: string, until: Date): Promise<UpdateResult | Document> {
|
||||
setThreadMessagesAsRead(rid: string, tmid: string, until: Date): Promise<UpdateResult | Document> {
|
||||
return this.updateMany(
|
||||
{
|
||||
tmid,
|
||||
rid,
|
||||
unread: true,
|
||||
ts: { $lt: until },
|
||||
tmid,
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
@ -1599,8 +1601,8 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
|
||||
|
||||
findVisibleUnreadMessagesByRoomAndDate(rid: string, after: Date): FindCursor<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>> {
|
||||
const query = {
|
||||
unread: true,
|
||||
rid,
|
||||
unread: true,
|
||||
$or: [
|
||||
{
|
||||
tmid: { $exists: false },
|
||||
@ -1624,13 +1626,15 @@ export class MessagesRaw extends BaseRaw<IMessage> implements IMessagesModel {
|
||||
}
|
||||
|
||||
findUnreadThreadMessagesByDate(
|
||||
rid: string,
|
||||
tmid: string,
|
||||
userId: string,
|
||||
after: Date,
|
||||
): FindCursor<Pick<IMessage, '_id' | 't' | 'pinned' | 'drid' | 'tmid'>> {
|
||||
const query = {
|
||||
'u._id': { $ne: userId },
|
||||
rid,
|
||||
'unread': true,
|
||||
'u._id': { $ne: userId },
|
||||
tmid,
|
||||
'tshow': { $exists: false },
|
||||
...(after && { ts: { $gt: after } }),
|
||||
|
||||
@ -2,7 +2,7 @@ import type {
|
||||
IMessage,
|
||||
IRoom,
|
||||
MessageAttachment,
|
||||
ReadReceipt,
|
||||
IReadReceiptWithUser,
|
||||
OtrSystemMessages,
|
||||
MessageUrl,
|
||||
IThreadMainMessage,
|
||||
@ -1022,7 +1022,7 @@ export type ChatEndpoints = {
|
||||
};
|
||||
};
|
||||
'/v1/chat.getMessageReadReceipts': {
|
||||
GET: (params: ChatGetMessageReadReceipts) => { receipts: ReadReceipt[] };
|
||||
GET: (params: ChatGetMessageReadReceipts) => { receipts: IReadReceiptWithUser[] };
|
||||
};
|
||||
'/v1/chat.getStarredMessages': {
|
||||
GET: (params: GetStarredMessages) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user