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