diff --git a/.changeset/unlucky-sloths-cough.md b/.changeset/unlucky-sloths-cough.md new file mode 100644 index 0000000000000..ce42ebea33969 --- /dev/null +++ b/.changeset/unlucky-sloths-cough.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat chat.getMessage API endpoints by migrating to a modern chained route definition syntax and utilizing shared AJV schemas for validation to enhance API documentation and ensure type safety through response validation. diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index f289960f4f411..84344d6be0379 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -10,7 +10,6 @@ import { isChatGetThreadsListProps, isChatDeleteProps, isChatSyncMessagesProps, - isChatGetMessageProps, isChatPostMessageProps, isChatSearchProps, isChatSendMessageProps, @@ -144,33 +143,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'chat.getMessage', - { - authRequired: true, - validateParams: isChatGetMessageProps, - }, - { - async get() { - if (!this.queryParams.msgId) { - return API.v1.failure('The "msgId" query parameter must be provided.'); - } - - const msg = await getSingleMessage(this.userId, this.queryParams.msgId); - - if (!msg) { - return API.v1.failure(); - } - - const [message] = await normalizeMessagesForUser([msg], this.userId); - - return API.v1.success({ - message, - }); - }, - }, -); - type ChatPinMessage = { messageId: IMessage['_id']; }; @@ -179,6 +151,10 @@ type ChatUnpinMessage = { messageId: IMessage['_id']; }; +type ChatGetMessage = { + msgId: IMessage['_id']; +}; + const ChatPinMessageSchema = { type: 'object', properties: { @@ -203,10 +179,24 @@ const ChatUnpinMessageSchema = { additionalProperties: false, }; +const ChatGetMessageSchema = { + type: 'object', + properties: { + msgId: { + type: 'string', + minLength: 1, + }, + }, + required: ['msgId'], + additionalProperties: false, +}; + const isChatPinMessageProps = ajv.compile(ChatPinMessageSchema); const isChatUnpinMessageProps = ajv.compile(ChatUnpinMessageSchema); +const isChatGetMessageProps = ajv.compile(ChatGetMessageSchema); + const chatEndpoints = API.v1 .post( 'chat.pinMessage', @@ -346,6 +336,47 @@ const chatEndpoints = API.v1 const updatedMessage = await Messages.findOneById(msg._id); const [message] = await normalizeMessagesForUser(updatedMessage ? [updatedMessage] : [], this.userId); + return API.v1.success({ + message, + }); + }, + ) + .get( + 'chat.getMessage', + { + authRequired: true, + query: isChatGetMessageProps, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 200: ajv.compile<{ message: IMessage }>({ + type: 'object', + properties: { + message: { $ref: '#/components/schemas/IMessage' }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['message', 'success'], + additionalProperties: false, + }), + }, + }, + + async function action() { + if (!this.queryParams.msgId) { + return API.v1.failure('The "msgId" query parameter must be provided.'); + } + + const msg: IMessage | null = await getSingleMessage(this.userId, this.queryParams.msgId); + + if (!msg) { + return API.v1.failure(); + } + + const [message]: IMessage[] = await normalizeMessagesForUser([msg], this.userId); + return API.v1.success({ message, }); diff --git a/apps/meteor/app/lib/server/functions/updateMessage.ts b/apps/meteor/app/lib/server/functions/updateMessage.ts index baf2628e73394..f5787cbce41b1 100644 --- a/apps/meteor/app/lib/server/functions/updateMessage.ts +++ b/apps/meteor/app/lib/server/functions/updateMessage.ts @@ -1,6 +1,7 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message } from '@rocket.chat/core-services'; import type { IMessage, IUser, AtLeast } from '@rocket.chat/core-typings'; +import type { Root } from '@rocket.chat/message-parser'; import { Messages, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -74,6 +75,27 @@ export const updateMessage = async function ( delete editedMessage.md; } + if (editedMessage.attachments != null) { + const attachments = Array.isArray(editedMessage.attachments) ? editedMessage.attachments : [editedMessage.attachments]; + + editedMessage.attachments = attachments.map((attachment) => { + let normalizedMd: Root; + + if (Array.isArray(attachment.md)) { + normalizedMd = attachment.md; + } else if (attachment.md != null) { + normalizedMd = [attachment.md]; + } else { + normalizedMd = []; + } + + return { + ...attachment, + md: normalizedMd, + }; + }); + } + // do not send $unset if not defined. Can cause exceptions in certain mongo versions. await Messages.updateOne( { _id }, diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index 71fd745d7f95c..1819a8745370a 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -115,24 +115,6 @@ const chatUnfollowMessageSchema = { export const isChatUnfollowMessageProps = ajv.compile(chatUnfollowMessageSchema); -type ChatGetMessage = { - msgId: IMessage['_id']; -}; - -const ChatGetMessageSchema = { - type: 'object', - properties: { - msgId: { - type: 'string', - minLength: 1, - }, - }, - required: ['msgId'], - additionalProperties: false, -}; - -export const isChatGetMessageProps = ajv.compile(ChatGetMessageSchema); - type ChatStarMessage = { messageId: IMessage['_id']; }; @@ -962,11 +944,6 @@ export type ChatEndpoints = { message: IMessage; }; }; - '/v1/chat.getMessage': { - GET: (params: ChatGetMessage) => { - message: IMessage; - }; - }; '/v1/chat.followMessage': { POST: (params: ChatFollowMessage) => void; };