From 8f7076babdedb6967761f1ee4a1d14fc73d09c35 Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Mon, 28 Jul 2025 19:24:52 +0300 Subject: [PATCH 1/3] Create fast-walls-turn.md --- .changeset/fast-walls-turn.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/fast-walls-turn.md diff --git a/.changeset/fast-walls-turn.md b/.changeset/fast-walls-turn.md new file mode 100644 index 0000000000000..20f49328ba4b7 --- /dev/null +++ b/.changeset/fast-walls-turn.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat chat.pinMessage 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. \ No newline at end of file From 290c1aafd057c0e89ef035a3991bf40ec6c33a9e Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Mon, 28 Jul 2025 19:26:09 +0300 Subject: [PATCH 2/3] refactor: update ChatPinMessage to the new pattern --- apps/meteor/app/api/server/v1/chat.ts | 100 ++++++++++++++++++++++---- packages/core-typings/src/Ajv.ts | 3 +- packages/rest-typings/src/v1/chat.ts | 23 ------ 3 files changed, 88 insertions(+), 38 deletions(-) diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index d22c9de0279e8..4484fb87cdb7b 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -2,6 +2,7 @@ import { Message } from '@rocket.chat/core-services'; import type { IMessage, IThreadMainMessage } from '@rocket.chat/core-typings'; import { Messages, Users, Rooms, Subscriptions } from '@rocket.chat/models'; import { + ajv, isChatReportMessageProps, isChatGetURLPreviewProps, isChatUpdateProps, @@ -9,7 +10,6 @@ import { isChatDeleteProps, isChatSyncMessagesProps, isChatGetMessageProps, - isChatPinMessageProps, isChatPostMessageProps, isChatSearchProps, isChatSendMessageProps, @@ -56,6 +56,7 @@ import { followMessage } from '../../../threads/server/methods/followMessage'; import { unfollowMessage } from '../../../threads/server/methods/unfollowMessage'; import { MessageTypes } from '../../../ui-utils/server'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; import { getPaginationItems } from '../helpers/getPaginationItems'; import { findDiscussionsFromRoom, findMentionedMessages, findStarredMessages } from '../lib/messages'; @@ -172,25 +173,87 @@ API.v1.addRoute( }, ); -API.v1.addRoute( +type ChatPinMessage = { + messageId: IMessage['_id']; +}; + +const ChatPinMessageSchema = { + type: 'object', + properties: { + messageId: { + type: 'string', + minLength: 1, + }, + }, + required: ['messageId'], + additionalProperties: false, +}; + +const isChatPinMessageProps = ajv.compile(ChatPinMessageSchema); + +const chatPinMessageEndpoints = API.v1.post( 'chat.pinMessage', - { authRequired: true, validateParams: isChatPinMessageProps }, { - async post() { - const msg = await Messages.findOneById(this.bodyParams.messageId); + authRequired: true, + body: isChatPinMessageProps, + response: { + 400: ajv.compile<{ + error?: string; + errorType?: string; + stack?: string; + details?: string; + }>({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [false] }, + stack: { type: 'string' }, + error: { type: 'string' }, + errorType: { type: 'string' }, + details: { type: 'string' }, + }, + required: ['success'], + additionalProperties: false, + }), + 401: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [false] }, + status: { type: 'string' }, + message: { type: 'string' }, + error: { type: 'string' }, + errorType: { type: 'string' }, + }, + required: ['success'], + additionalProperties: false, + }), + 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() { + const msg = await Messages.findOneById(this.bodyParams.messageId); - if (!msg) { - throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.'); - } + if (!msg) { + throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.'); + } - const pinnedMessage = await pinMessage(msg, this.userId); + const pinnedMessage = await pinMessage(msg, this.userId); - const [message] = await normalizeMessagesForUser([pinnedMessage], this.userId); + const [message] = await normalizeMessagesForUser([pinnedMessage], this.userId); - return API.v1.success({ - message, - }); - }, + return API.v1.success({ + message, + }); }, ); @@ -845,3 +908,12 @@ API.v1.addRoute( }, }, ); + +type ChatPinMessageEndpoints = ExtractRoutesFromAPI; + +export type ChatEndpoints = ChatPinMessageEndpoints; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends ChatPinMessageEndpoints {} +} diff --git a/packages/core-typings/src/Ajv.ts b/packages/core-typings/src/Ajv.ts index aa13b4c895b98..1e227ec4c3e22 100644 --- a/packages/core-typings/src/Ajv.ts +++ b/packages/core-typings/src/Ajv.ts @@ -2,6 +2,7 @@ import typia from 'typia'; import type { ICustomSound } from './ICustomSound'; import type { IInvite } from './IInvite'; +import type { IMessage } from './IMessage'; import type { ISubscription } from './ISubscription'; -export const schemas = typia.json.schemas<[ISubscription | IInvite | ICustomSound], '3.0'>(); +export const schemas = typia.json.schemas<[ISubscription | IInvite | ICustomSound | IMessage], '3.0'>(); diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index 5e9e0ff6ec882..9f0052c26e997 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -175,24 +175,6 @@ const ChatUnstarMessageSchema = { export const isChatUnstarMessageProps = ajv.compile(ChatUnstarMessageSchema); -type ChatPinMessage = { - messageId: IMessage['_id']; -}; - -const ChatPinMessageSchema = { - type: 'object', - properties: { - messageId: { - type: 'string', - minLength: 1, - }, - }, - required: ['messageId'], - additionalProperties: false, -}; - -export const isChatPinMessageProps = ajv.compile(ChatPinMessageSchema); - type ChatUnpinMessage = { messageId: IMessage['_id']; }; @@ -952,11 +934,6 @@ export type ChatEndpoints = { '/v1/chat.unStarMessage': { POST: (params: ChatUnstarMessage) => void; }; - '/v1/chat.pinMessage': { - POST: (params: ChatPinMessage) => { - message: IMessage; - }; - }; '/v1/chat.unPinMessage': { POST: (params: ChatUnpinMessage) => void; }; From cf5c646a4831b46ac02302043977fe8f4fe4fc3d Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Tue, 29 Jul 2025 16:12:13 +0300 Subject: [PATCH 3/3] fix: remove unnecessary undefined type from reactions names in IMessage interface becouse it create an error in typia --- packages/core-typings/src/IMessage/IMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-typings/src/IMessage/IMessage.ts b/packages/core-typings/src/IMessage/IMessage.ts index d5a2ee518d324..5ed03ce466d05 100644 --- a/packages/core-typings/src/IMessage/IMessage.ts +++ b/packages/core-typings/src/IMessage/IMessage.ts @@ -195,7 +195,7 @@ export interface IMessage extends IRocketChatRecord { attachments?: MessageAttachment[]; reactions?: { - [key: string]: { names?: (string | undefined)[]; usernames: string[]; federationReactionEventIds?: Record }; + [key: string]: { names?: string[]; usernames: string[]; federationReactionEventIds?: Record }; }; private?: boolean;