refactor: remove Meteor calls for message history methods (#34204)

Co-authored-by: Ricardo Garim <9621276+ricardogarim@users.noreply.github.com>
pull/35132/head^2
Marcos Spessatto Defendi 11 months ago committed by GitHub
parent 77ba1602a4
commit 1da75e76e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      apps/meteor/app/api/server/v1/channels.ts
  2. 9
      apps/meteor/app/api/server/v1/chat.ts
  3. 4
      apps/meteor/app/api/server/v1/groups.ts
  4. 6
      apps/meteor/app/api/server/v1/im.ts
  5. 242
      apps/meteor/app/lib/server/methods/getChannelHistory.ts
  6. 165
      apps/meteor/server/publications/messages.ts
  7. 3
      apps/meteor/tests/unit/server/publications/messages.spec.ts

@ -36,6 +36,7 @@ import { saveRoomSettings } from '../../../channel-settings/server/methods/saveR
import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission';
import { addUsersToRoomMethod } from '../../../lib/server/methods/addUsersToRoom';
import { createChannelMethod } from '../../../lib/server/methods/createChannel';
import { getChannelHistory } from '../../../lib/server/methods/getChannelHistory';
import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom';
import { settings } from '../../../settings/server';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
@ -163,10 +164,11 @@ API.v1.addRoute(
const { count = 20, offset = 0 } = await getPaginationItems(this.queryParams);
const result = await Meteor.callAsync('getChannelHistory', {
const result = await getChannelHistory({
rid: findResult._id,
fromUserId: this.userId,
latest: latest ? new Date(latest) : new Date(),
oldest: oldest && new Date(oldest),
oldest: oldest ? new Date(oldest) : undefined,
inclusive: inclusive === 'true',
offset,
count,

@ -14,6 +14,7 @@ import { Meteor } from 'meteor/meteor';
import { reportMessage } from '../../../../server/lib/moderation/reportMessage';
import { messageSearch } from '../../../../server/methods/messageSearch';
import { getMessageHistory } from '../../../../server/publications/messages';
import { roomAccessAttributes } from '../../../authorization/server';
import { canAccessRoomAsync, canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage';
@ -101,7 +102,7 @@ API.v1.addRoute(
...(type && { type }),
};
const result = await Meteor.callAsync('messages/get', roomId, getMessagesQuery);
const result = await getMessageHistory(roomId, this.userId, getMessagesQuery);
if (!result) {
return API.v1.failure();
@ -109,9 +110,9 @@ API.v1.addRoute(
return API.v1.success({
result: {
...(result.updated && { updated: await normalizeMessagesForUser(result.updated, this.userId) }),
...(result.deleted && { deleted: result.deleted }),
...(result.cursor && { cursor: result.cursor }),
updated: 'updated' in result ? await normalizeMessagesForUser(result.updated, this.userId) : [],
deleted: 'deleted' in result ? result.deleted : [],
cursor: 'cursor' in result ? result.cursor : undefined,
},
});
},

@ -16,6 +16,7 @@ import { hasAllPermissionAsync, hasPermissionAsync } from '../../../authorizatio
import { saveRoomSettings } from '../../../channel-settings/server/methods/saveRoomSettings';
import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission';
import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup';
import { getChannelHistory } from '../../../lib/server/methods/getChannelHistory';
import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import { API } from '../api';
@ -505,8 +506,9 @@ API.v1.addRoute(
const showThreadMessages = this.queryParams.showThreadMessages !== 'false';
const result = await Meteor.callAsync('getChannelHistory', {
const result = await getChannelHistory({
rid: findResult.rid,
fromUserId: this.userId,
latest: latestDate,
oldest: oldestDate,
inclusive,

@ -21,6 +21,7 @@ import { canAccessRoomIdAsync } from '../../../authorization/server/functions/ca
import { hasAtLeastOnePermissionAsync, hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { saveRoomSettings } from '../../../channel-settings/server/methods/saveRoomSettings';
import { getRoomByNameOrIdWithOptionToJoin } from '../../../lib/server/functions/getRoomByNameOrIdWithOptionToJoin';
import { getChannelHistory } from '../../../lib/server/methods/getChannelHistory';
import { settings } from '../../../settings/server';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import { API } from '../api';
@ -277,8 +278,9 @@ API.v1.addRoute(
const objectParams = {
rid: room._id,
fromUserId: this.userId,
latest: latest ? new Date(latest) : new Date(),
oldest: oldest && new Date(oldest),
oldest: oldest ? new Date(oldest) : undefined,
inclusive: inclusive === 'true',
offset,
count,
@ -286,7 +288,7 @@ API.v1.addRoute(
showThreadMessages: showThreadMessages === 'true',
};
const result = await Meteor.callAsync('getChannelHistory', objectParams);
const result = await getChannelHistory(objectParams);
if (!result) {
return API.v1.forbidden();

@ -26,125 +26,159 @@ declare module '@rocket.chat/ddp-client' {
}
}
Meteor.methods<ServerMethods>({
async getChannelHistory({ rid, latest, oldest, inclusive, offset = 0, count = 20, unreads, showThreadMessages = true }) {
check(rid, String);
export const getChannelHistory = async ({
rid,
fromUserId,
latest,
oldest,
inclusive,
offset = 0,
count = 20,
unreads,
showThreadMessages = true,
}: {
rid: string;
fromUserId: string;
latest?: Date;
oldest?: Date;
inclusive?: boolean;
offset?: number;
count?: number;
unreads?: boolean;
showThreadMessages?: boolean;
}): Promise<false | IMessage[] | { messages: IMessage[]; firstUnread?: any; unreadNotLoaded?: number }> => {
check(rid, String);
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getChannelHistory' });
}
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getChannelHistory' });
}
if (!fromUserId) {
return false;
}
const fromUserId = Meteor.userId();
if (!fromUserId) {
return false;
}
const room = await Rooms.findOneById(rid);
if (!room) {
return false;
}
const room = await Rooms.findOneById(rid);
if (!room) {
return false;
}
if (!(await canAccessRoomAsync(room, { _id: fromUserId }))) {
return false;
}
if (!(await canAccessRoomAsync(room, { _id: fromUserId }))) {
return false;
}
// Make sure they can access the room
if (
room.t === 'c' &&
!(await hasPermissionAsync(fromUserId, 'preview-c-room')) &&
!(await Subscriptions.findOneByRoomIdAndUserId(rid, fromUserId, { projection: { _id: 1 } }))
) {
return false;
}
// Make sure they can access the room
if (
room.t === 'c' &&
!(await hasPermissionAsync(fromUserId, 'preview-c-room')) &&
!(await Subscriptions.findOneByRoomIdAndUserId(rid, fromUserId, { projection: { _id: 1 } }))
) {
return false;
}
// Ensure latest is always defined.
if (latest === undefined) {
latest = new Date();
}
// Ensure latest is always defined.
if (latest === undefined) {
latest = new Date();
}
// Verify oldest is a date if it exists
// Verify oldest is a date if it exists
if (oldest !== undefined && {}.toString.call(oldest) !== '[object Date]') {
throw new Meteor.Error('error-invalid-date', 'Invalid date', { method: 'getChannelHistory' });
}
if (oldest !== undefined && {}.toString.call(oldest) !== '[object Date]') {
throw new Meteor.Error('error-invalid-date', 'Invalid date', { method: 'getChannelHistory' });
const hiddenSystemMessages = settings.get<MessageTypesValues[]>('Hide_System_Messages');
const hiddenMessageTypes = getHiddenSystemMessages(room, hiddenSystemMessages);
const options: Record<string, unknown> = {
sort: {
ts: -1,
},
skip: offset,
limit: count,
};
const records =
oldest === undefined
? await Messages.findVisibleByRoomIdBeforeTimestampNotContainingTypes(
rid,
latest,
hiddenMessageTypes,
options,
showThreadMessages,
inclusive,
).toArray()
: await Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
oldest,
latest,
hiddenMessageTypes,
options,
showThreadMessages,
inclusive,
).toArray();
const messages = await normalizeMessagesForUser(records, fromUserId);
if (unreads) {
let unreadNotLoaded = 0;
let firstUnread = undefined;
if (oldest !== undefined) {
const firstMsg = messages[messages.length - 1];
if (firstMsg !== undefined && firstMsg.ts > oldest) {
const unreadMessages = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
oldest,
firstMsg.ts,
hiddenMessageTypes,
{
limit: 1,
sort: {
ts: 1,
},
},
showThreadMessages,
);
const totalCursor = await Messages.countVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
oldest,
firstMsg.ts,
hiddenMessageTypes,
showThreadMessages,
);
firstUnread = (await unreadMessages.toArray())[0];
unreadNotLoaded = totalCursor;
}
}
const hiddenSystemMessages = settings.get<MessageTypesValues[]>('Hide_System_Messages');
return {
messages: messages || [],
firstUnread,
unreadNotLoaded,
};
}
const hiddenMessageTypes = getHiddenSystemMessages(room, hiddenSystemMessages);
return {
messages: messages || [],
};
};
const options: Record<string, unknown> = {
sort: {
ts: -1,
},
skip: offset,
limit: count,
};
Meteor.methods<ServerMethods>({
async getChannelHistory({ rid, latest, oldest, inclusive, offset = 0, count = 20, unreads, showThreadMessages = true }) {
check(rid, String);
const records =
oldest === undefined
? await Messages.findVisibleByRoomIdBeforeTimestampNotContainingTypes(
rid,
latest,
hiddenMessageTypes,
options,
showThreadMessages,
inclusive,
).toArray()
: await Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
oldest,
latest,
hiddenMessageTypes,
options,
showThreadMessages,
inclusive,
).toArray();
const messages = await normalizeMessagesForUser(records, fromUserId);
if (unreads) {
let unreadNotLoaded = 0;
let firstUnread = undefined;
if (oldest !== undefined) {
const firstMsg = messages[messages.length - 1];
if (firstMsg !== undefined && firstMsg.ts > oldest) {
const unreadMessages = Messages.findVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
oldest,
firstMsg.ts,
hiddenMessageTypes,
{
limit: 1,
sort: {
ts: 1,
},
},
showThreadMessages,
);
const totalCursor = await Messages.countVisibleByRoomIdBetweenTimestampsNotContainingTypes(
rid,
oldest,
firstMsg.ts,
hiddenMessageTypes,
showThreadMessages,
);
firstUnread = (await unreadMessages.toArray())[0];
unreadNotLoaded = totalCursor;
}
}
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getChannelHistory' });
}
return {
messages: messages || [],
firstUnread,
unreadNotLoaded,
};
const fromUserId = Meteor.userId();
if (!fromUserId) {
return false;
}
return {
messages: messages || [],
};
return getChannelHistory({ rid, fromUserId, latest, oldest, inclusive, offset, count, unreads, showThreadMessages });
},
});

@ -6,6 +6,7 @@ import { Meteor } from 'meteor/meteor';
import type { FindOptions } from 'mongodb';
import { canAccessRoomIdAsync } from '../../app/authorization/server/functions/canAccessRoom';
import { getChannelHistory } from '../../app/lib/server/methods/getChannelHistory';
type CursorPaginationType = 'UPDATED' | 'DELETED';
@ -25,14 +26,19 @@ declare module '@rocket.chat/ddp-client' {
previous?: string;
type?: CursorPaginationType;
},
) => Promise<{
updated: IMessage[];
deleted: IMessage[];
cursor: {
next: string | null;
previous: string | null;
};
}>;
) => Promise<
| {
updated: IMessage[];
deleted: IMessage[];
cursor?: {
next: string | null;
previous: string | null;
};
}
| boolean
| IMessage[]
| { messages: IMessage[]; firstUnread?: any; unreadNotLoaded?: number }
>;
}
}
@ -155,13 +161,23 @@ export async function handleCursorPagination(
count: number,
next?: string,
previous?: string,
) {
): Promise<{
updated: IMessage[];
deleted: IMessage[];
cursor?: {
next: string | null;
previous: string | null;
};
}> {
const { query, options } = mountCursorQuery({ next, previous, count });
const response =
type === 'UPDATED'
? await Messages.findForUpdates(rid, query, options).toArray()
: ((await Messages.trashFind({ rid, _deletedAt: query }, { projection: { _id: 1, _deletedAt: 1 }, ...options })!.toArray()) ?? []);
: ((await Messages.trashFind(
{ rid, _deletedAt: query },
{ projection: { _id: 1, _deletedAt: 1 }, ...options },
)!.toArray()) as IMessage[]);
const cursor = {
next: mountNextCursor(response, count, type, next, previous),
@ -173,11 +189,89 @@ export async function handleCursorPagination(
}
return {
[type.toLowerCase()]: response,
updated: type === 'UPDATED' ? response : [],
deleted: type === 'DELETED' ? response : [],
cursor,
};
}
export const getMessageHistory = async (
rid: IRoom['_id'],
fromId: string,
{
lastUpdate,
latestDate = new Date(),
oldestDate,
inclusive = false,
count = 20,
unreads = false,
next,
previous,
type,
}: {
lastUpdate?: Date;
latestDate?: Date;
oldestDate?: Date;
inclusive?: boolean;
count?: number;
unreads?: boolean;
next?: string;
previous?: string;
type?: CursorPaginationType;
},
): Promise<
| {
updated: IMessage[];
deleted: IMessage[];
cursor?: {
next: string | null;
previous: string | null;
};
}
| false
| IMessage[]
| { messages: IMessage[]; firstUnread?: any; unreadNotLoaded?: number }
> => {
if (!(await canAccessRoomIdAsync(rid, fromId))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'messages/get' });
}
if (type && !['UPDATED', 'DELETED'].includes(type)) {
throw new Meteor.Error('error-type-param-not-supported', 'The "type" parameter must be either "UPDATED" or "DELETED"');
}
if ((next || previous) && !type) {
throw new Meteor.Error('error-type-param-required', 'The "type" parameter is required when using the "next" or "previous" parameters');
}
if (next && previous) {
throw new Meteor.Error('error-cursor-conflict', 'You cannot provide both "next" and "previous" parameters');
}
if ((next || previous) && lastUpdate) {
throw new Meteor.Error(
'error-cursor-and-lastUpdate-conflict',
'The attributes "next", "previous" and "lastUpdate" cannot be used together',
);
}
const hasCursorPagination = !!((next || previous) && count !== null && type);
if (!hasCursorPagination && !lastUpdate) {
return getChannelHistory({ rid, fromUserId: fromId, latest: latestDate, oldest: oldestDate, inclusive, count, unreads });
}
if (lastUpdate) {
return handleWithoutPagination(rid, lastUpdate);
}
if (!type) {
throw new Meteor.Error('error-param-required', 'The "type" or "lastUpdate" parameters must be provided');
}
return handleCursorPagination(type, rid, count, next, previous);
};
Meteor.methods<ServerMethods>({
async 'messages/get'(
rid,
@ -195,53 +289,6 @@ Meteor.methods<ServerMethods>({
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'messages/get' });
}
if (!(await canAccessRoomIdAsync(rid, fromId))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'messages/get' });
}
if (type && !['UPDATED', 'DELETED'].includes(type)) {
throw new Meteor.Error('error-type-param-not-supported', 'The "type" parameter must be either "UPDATED" or "DELETED"');
}
if ((next || previous) && !type) {
throw new Meteor.Error(
'error-type-param-required',
'The "type" parameter is required when using the "next" or "previous" parameters',
);
}
if (next && previous) {
throw new Meteor.Error('error-cursor-conflict', 'You cannot provide both "next" and "previous" parameters');
}
if ((next || previous) && lastUpdate) {
throw new Meteor.Error(
'error-cursor-and-lastUpdate-conflict',
'The attributes "next", "previous" and "lastUpdate" cannot be used together',
);
}
const hasCursorPagination = !!((next || previous) && count !== null && type);
if (!hasCursorPagination && !lastUpdate) {
return Meteor.callAsync('getChannelHistory', {
rid,
latest: latestDate,
oldest: oldestDate,
inclusive,
count,
unreads,
});
}
if (lastUpdate) {
return handleWithoutPagination(rid, lastUpdate);
}
if (!type) {
throw new Meteor.Error('error-param-required', 'The "type" or "lastUpdate" parameters must be provided');
}
return handleCursorPagination(type, rid, count, next, previous);
return getMessageHistory(rid, fromId, { lastUpdate, latestDate, oldestDate, inclusive, count, unreads, next, previous, type });
},
});

@ -33,6 +33,9 @@ const {
'meteor/check': {
check: sinon.stub(),
},
'../../app/lib/server/methods/getChannelHistory': {
getChannelHistory: sinon.stub(),
},
'meteor/meteor': {
'Meteor': {
methods: sinon.stub(),

Loading…
Cancel
Save