Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/nine-news-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/rest-typings': minor
'@rocket.chat/meteor': minor
---

Migrate groups.history with AJV validation and schema types
199 changes: 139 additions & 60 deletions apps/meteor/app/api/server/v1/groups.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Team, isMeteorError } from '@rocket.chat/core-services';
import type { IIntegration, IUser, IRoom, RoomType, UserStatus } from '@rocket.chat/core-typings';
import type { IIntegration, IUser, IRoom, RoomType, UserStatus, IMessage } from '@rocket.chat/core-typings';
import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models';
import { isGroupsOnlineProps, isGroupsMessagesProps, isGroupsFilesProps } from '@rocket.chat/rest-typings';
import type { PaginatedRequest, GroupsBaseProps } from '@rocket.chat/rest-typings';
import {
isGroupsOnlineProps,
isGroupsMessagesProps,
isGroupsFilesProps,
ajv,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
withGroupBaseProperties,
} from '@rocket.chat/rest-typings';
import { isTruthy } from '@rocket.chat/tools';
import { check, Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
Expand Down Expand Up @@ -31,12 +41,132 @@ import { executeGetRoomRoles } from '../../../lib/server/methods/getRoomRoles';
import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom';
import { executeUnarchiveRoom } from '../../../lib/server/methods/unarchiveRoom';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';
import { addUserToFileObj } from '../helpers/addUserToFileObj';
import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFromParams';

type GroupsHistoryProps = PaginatedRequest<
GroupsBaseProps & {
latest?: string;
oldest?: string;
inclusive?: 'true' | 'false';
unreads?: 'true' | 'false';
showThreadMessages?: string;
}
>;
const groupsHistoryPropsSchema = withGroupBaseProperties({
latest: {
type: 'string',
},
oldest: {
type: 'string',
},
inclusive: {
type: 'string',
},
unreads: {
type: 'string',
},
showThreadMessages: {
type: 'string',
},
count: {
type: 'integer',
},
offset: {
type: 'integer',
},
sort: {
type: 'string',
},
});
const isGroupsHistoryProps = ajv.compile<GroupsHistoryProps>(groupsHistoryPropsSchema);

const isGroupsHistoryResponse = ajv.compile({
type: 'object',
properties: {
success: { type: 'boolean', enum: [true] },
messages: {
type: 'array',
items: { $ref: '#/components/schemas/IMessage' },
},
count: { type: 'integer' },
offset: { type: 'integer' },
total: { type: 'integer' },
unreadNotLoaded: { type: 'integer' },
firstUnread: { $ref: '#/components/schemas/IMessage' },
},
required: ['success', 'messages'],
additionalProperties: false,
});

const groupsHistoryEndpoints = API.v1.get(
'groups.history',
{
authRequired: true,
query: isGroupsHistoryProps,
response: {
200: isGroupsHistoryResponse,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
checkedArchived: false,
});

let latestDate = new Date();
if (this.queryParams.latest) {
latestDate = new Date(this.queryParams.latest);
}

let oldestDate = undefined;
if (this.queryParams.oldest) {
oldestDate = new Date(this.queryParams.oldest);
}

const inclusive = this.queryParams.inclusive === 'true';

let count = 20;
if (this.queryParams.count) {
count = parseInt(String(this.queryParams.count));
}

let offset = 0;
if (this.queryParams.offset) {
offset = parseInt(String(this.queryParams.offset));
}

const unreads = this.queryParams.unreads === 'true';
const showThreadMessages = this.queryParams.showThreadMessages !== 'false';

const result = await getChannelHistory({
rid: findResult.rid,
fromUserId: this.userId,
latest: latestDate,
oldest: oldestDate,
inclusive,
offset,
count,
unreads,
showThreadMessages,
});

if (!result) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}

return API.v1.success(result as { messages: IMessage[]; firstUnread?: IMessage; unreadNotLoaded?: number });
},
);

async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise<IRoom> {
if (
(!('roomId' in params) && !('roomName' in params)) ||
Expand Down Expand Up @@ -488,64 +618,6 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'groups.history',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
checkedArchived: false,
});

let latestDate = new Date();
if (this.queryParams.latest) {
latestDate = new Date(this.queryParams.latest);
}

let oldestDate = undefined;
if (this.queryParams.oldest) {
oldestDate = new Date(this.queryParams.oldest);
}

const inclusive = this.queryParams.inclusive === 'true';

let count = 20;
if (this.queryParams.count) {
count = parseInt(String(this.queryParams.count));
}

let offset = 0;
if (this.queryParams.offset) {
offset = parseInt(String(this.queryParams.offset));
}

const unreads = this.queryParams.unreads === 'true';

const showThreadMessages = this.queryParams.showThreadMessages !== 'false';

const result = await getChannelHistory({
rid: findResult.rid,
fromUserId: this.userId,
latest: latestDate,
oldest: oldestDate,
inclusive,
offset,
count,
unreads,
showThreadMessages,
});

if (!result) {
return API.v1.forbidden();
}

return API.v1.success(result);
},
},
);

API.v1.addRoute(
'groups.info',
{ authRequired: true },
Expand Down Expand Up @@ -1300,3 +1372,10 @@ API.v1.addRoute(
},
},
);

export type GroupsHistoryEndpoints = ExtractRoutesFromAPI<typeof groupsHistoryEndpoints>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends GroupsHistoryEndpoints {}
}
49 changes: 0 additions & 49 deletions packages/rest-typings/src/v1/groups/GroupsHistoryProps.ts

This file was deleted.

6 changes: 0 additions & 6 deletions packages/rest-typings/src/v1/groups/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type { GroupsCreateProps } from './GroupsCreateProps';
import type { GroupsDeleteProps } from './GroupsDeleteProps';
import type { GroupsFilesProps } from './GroupsFilesProps';
import type { GroupsGetIntegrationsProps } from './GroupsGetIntegrationsProps';
import type { GroupsHistoryProps } from './GroupsHistoryProps';
import type { GroupsInfoProps } from './GroupsInfoProps';
import type { GroupsInviteProps } from './GroupsInviteProps';
import type { GroupsKickProps } from './GroupsKickProps';
Expand Down Expand Up @@ -54,11 +53,6 @@ export type GroupsEndpoints = {
total: number;
};
};
'/v1/groups.history': {
GET: (params: GroupsHistoryProps) => PaginatedResult<{
messages: IMessage[];
}>;
};
'/v1/groups.archive': {
POST: (params: GroupsArchiveProps) => void;
};
Expand Down
4 changes: 2 additions & 2 deletions packages/rest-typings/src/v1/groups/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type * from './groups';

export * from './BaseProps';
export * from './GroupsArchiveProps';
export * from './GroupsCloseProps';
export * from './GroupsConvertToTeamProps';
Expand Down Expand Up @@ -35,5 +36,4 @@ export * from './GroupsSetPurposeProps';
export * from './GroupsSetReadOnlyProps';
export * from './GroupsSetTopicProps';
export * from './GroupsSetTypeProps';
export * from './GroupsModeratorsProps';
export * from './GroupsHistoryProps';
export * from './GroupsModeratorsProps';