RocketChat/Rocket.Chat

View on GitHub
apps/meteor/app/lib/server/methods/getChannelHistory.ts

Summary

Maintainability
C
1 day
Test Coverage
import type { IMessage, MessageTypesValues } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Messages, Subscriptions, Rooms } from '@rocket.chat/models';
import { check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';

import { canAccessRoomAsync } from '../../../authorization/server';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { settings } from '../../../settings/server/cached';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import { getHiddenSystemMessages } from '../lib/getHiddenSystemMessages';

declare module '@rocket.chat/ddp-client' {
    // eslint-disable-next-line @typescript-eslint/naming-convention
    interface ServerMethods {
        getChannelHistory(params: {
            rid: string;
            latest?: Date;
            oldest?: Date;
            inclusive?: boolean;
            offset?: number;
            count?: number;
            unreads?: boolean;
            showThreadMessages?: boolean;
        }): boolean | IMessage[] | { messages: IMessage[]; firstUnread?: any; unreadNotLoaded?: number };
    }
}

Meteor.methods<ServerMethods>({
    async getChannelHistory({ rid, latest, oldest, inclusive, offset = 0, count = 20, unreads, showThreadMessages = true }) {
        check(rid, String);

        if (!Meteor.userId()) {
            throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getChannelHistory' });
        }

        const fromUserId = Meteor.userId();
        if (!fromUserId) {
            return false;
        }

        const room = await Rooms.findOneById(rid);
        if (!room) {
            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;
        }

        // Ensure latest is always defined.
        if (latest === undefined) {
            latest = new Date();
        }

        // Verify oldest is a date if it exists
        if (oldest !== undefined && !_.isDate(oldest)) {
            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;
                }
            }

            return {
                messages: messages || [],
                firstUnread,
                unreadNotLoaded,
            };
        }

        return {
            messages: messages || [],
        };
    },
});