diff --git a/.changeset/migrate-chat-getThreadMessages-to-openapi.md b/.changeset/migrate-chat-getThreadMessages-to-openapi.md new file mode 100644 index 0000000000000..999ca975edf05 --- /dev/null +++ b/.changeset/migrate-chat-getThreadMessages-to-openapi.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/meteor': minor +'@rocket.chat/rest-typings': minor +--- + +Migrated chat.getThreadMessages endpoint to new OpenAPI pattern with AJV validation diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index a00d57e46ae72..8bb2e0cafe965 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -20,12 +20,12 @@ import { isChatReactProps, isChatGetDeletedMessagesProps, isChatSyncThreadsListProps, - isChatGetThreadMessagesProps, isChatSyncThreadMessagesProps, isChatGetStarredMessagesProps, isChatGetDiscussionsProps, validateBadRequestErrorResponse, validateUnauthorizedErrorResponse, + type PaginatedRequest, } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Meteor } from 'meteor/meteor'; @@ -275,6 +275,44 @@ const isChatPinMessageProps = ajv.compile(ChatPinMessageSchema); const isChatUnpinMessageProps = ajv.compile(ChatUnpinMessageSchema); +type ChatGetThreadMessages = PaginatedRequest<{ + tmid: string; +}>; + +const ChatGetThreadMessagesSchema = { + type: 'object', + properties: { + tmid: { + type: 'string', + minLength: 1, + }, + count: { + type: 'number', + nullable: true, + }, + offset: { + type: 'number', + nullable: true, + }, + sort: { + type: 'string', + nullable: true, + }, + query: { + type: 'string', + nullable: true, + }, + fields: { + type: 'string', + nullable: true, + }, + }, + required: ['tmid'], + additionalProperties: false, +}; + +const isChatGetThreadMessagesLocalProps = ajv.compile(ChatGetThreadMessagesSchema); + const chatEndpoints = API.v1 .post( 'chat.pinMessage', @@ -558,6 +596,76 @@ const chatEndpoints = API.v1 return API.v1.success(); }, + ) + .get( + 'chat.getThreadMessages', + { + authRequired: true, + query: isChatGetThreadMessagesLocalProps, + response: { + 200: ajv.compile<{ + messages: IMessage[]; + count: number; + offset: number; + total: number; + success: boolean; + }>({ + type: 'object', + properties: { + messages: { + type: 'array', + items: { $ref: '#/components/schemas/IMessage' }, + }, + count: { type: 'number' }, + offset: { type: 'number' }, + total: { type: 'number' }, + success: { type: 'boolean', enum: [true] }, + }, + required: ['messages', 'count', 'offset', 'total', 'success'], + additionalProperties: false, + }), + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + }, + }, + async function action() { + const { tmid } = this.queryParams; + const { query, fields, sort } = await this.parseJsonQuery(); + const { offset, count } = await getPaginationItems(this.queryParams); + + if (!settings.get('Threads_enabled')) { + throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); + } + + const thread = await Messages.findOneById(tmid, { projection: { rid: 1 } }); + if (!thread?.rid) { + throw new Meteor.Error('error-invalid-message', 'Invalid Message'); + } + const user = await Users.findOneById(this.userId, { projection: { _id: 1 } }); + const room = await Rooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } }); + + if (!room || !user || !(await canAccessRoomAsync(room, user))) { + throw new Meteor.Error('error-not-allowed', 'Not Allowed'); + } + const { cursor, totalCount } = Messages.findPaginated( + { ...query, tmid }, + { + sort: sort || { ts: 1 }, + skip: offset, + limit: count, + projection: fields, + }, + ); + + const [messages, total] = await Promise.all([cursor.toArray(), totalCount]); + + return API.v1.success({ + messages, + count: messages.length, + offset, + total, + }); + }, ); API.v1.addRoute( @@ -873,51 +981,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'chat.getThreadMessages', - { authRequired: true, validateParams: isChatGetThreadMessagesProps }, - { - async get() { - const { tmid } = this.queryParams; - const { query, fields, sort } = await this.parseJsonQuery(); - const { offset, count } = await getPaginationItems(this.queryParams); - - if (!settings.get('Threads_enabled')) { - throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); - } - - const thread = await Messages.findOneById(tmid, { projection: { rid: 1 } }); - if (!thread?.rid) { - throw new Meteor.Error('error-invalid-message', 'Invalid Message'); - } - const user = await Users.findOneById(this.userId, { projection: { _id: 1 } }); - const room = await Rooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } }); - - if (!room || !user || !(await canAccessRoomAsync(room, user))) { - throw new Meteor.Error('error-not-allowed', 'Not Allowed'); - } - const { cursor, totalCount } = Messages.findPaginated( - { ...query, tmid }, - { - sort: sort || { ts: 1 }, - skip: offset, - limit: count, - projection: fields, - }, - ); - - const [messages, total] = await Promise.all([cursor.toArray(), totalCount]); - - return API.v1.success({ - messages, - count: messages.length, - offset, - total, - }); - }, - }, -); - API.v1.addRoute( 'chat.syncThreadMessages', { authRequired: true, validateParams: isChatSyncThreadMessagesProps }, diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index b52bba2d61ed5..cb4ff1d397a6f 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -673,36 +673,6 @@ const ChatSyncThreadMessagesSchema = { export const isChatSyncThreadMessagesProps = ajv.compile(ChatSyncThreadMessagesSchema); -type ChatGetThreadMessages = PaginatedRequest<{ - tmid: string; -}>; - -const ChatGetThreadMessagesSchema = { - type: 'object', - properties: { - tmid: { - type: 'string', - minLength: 1, - }, - count: { - type: 'number', - nullable: true, - }, - offset: { - type: 'number', - nullable: true, - }, - sort: { - type: 'string', - nullable: true, - }, - }, - required: ['tmid'], - additionalProperties: false, -}; - -export const isChatGetThreadMessagesProps = ajv.compile(ChatGetThreadMessagesSchema); - type ChatGetDeletedMessages = PaginatedRequest<{ roomId: IRoom['_id']; since: string; @@ -997,14 +967,6 @@ export type ChatEndpoints = { }; }; }; - '/v1/chat.getThreadMessages': { - GET: (params: ChatGetThreadMessages) => { - messages: IMessage[]; - count: number; - offset: number; - total: number; - }; - }; '/v1/chat.getDeletedMessages': { GET: (params: ChatGetDeletedMessages) => { messages: IMessage[];