Skip to content

Commit 2e4fe2b

Browse files
chore: Add OpenAPI support for the Rocket.Chat chat.postMessage API endpoints
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.
1 parent 29d2454 commit 2e4fe2b

File tree

3 files changed

+175
-146
lines changed

3 files changed

+175
-146
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": minor
3+
"@rocket.chat/rest-typings": minor
4+
---
5+
6+
Add OpenAPI support for the Rocket.Chat chat.postMessage 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.

apps/meteor/app/api/server/v1/chat.ts

Lines changed: 168 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Message } from '@rocket.chat/core-services';
2-
import type { IMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
2+
import type { IMessage, IUser, IThreadMainMessage, MessageAttachment, RequiredField } from '@rocket.chat/core-typings';
33
import { MessageTypes } from '@rocket.chat/message-types';
44
import { Messages, Users, Rooms, Subscriptions } from '@rocket.chat/models';
55
import {
@@ -11,7 +11,6 @@ import {
1111
isChatDeleteProps,
1212
isChatSyncMessagesProps,
1313
isChatGetMessageProps,
14-
isChatPostMessageProps,
1514
isChatSearchProps,
1615
isChatSendMessageProps,
1716
isChatStarMessageProps,
@@ -179,6 +178,26 @@ type ChatUnpinMessage = {
179178
messageId: IMessage['_id'];
180179
};
181180

181+
type ChatPostMessage =
182+
| {
183+
roomId: string | string[];
184+
text?: string;
185+
alias?: string;
186+
emoji?: string;
187+
avatar?: string;
188+
attachments?: MessageAttachment[];
189+
customFields?: IMessage['customFields'];
190+
}
191+
| {
192+
channel: string | string[];
193+
text?: string;
194+
alias?: string;
195+
emoji?: string;
196+
avatar?: string;
197+
attachments?: MessageAttachment[];
198+
customFields?: IMessage['customFields'];
199+
};
200+
182201
const ChatPinMessageSchema = {
183202
type: 'object',
184203
properties: {
@@ -203,10 +222,116 @@ const ChatUnpinMessageSchema = {
203222
additionalProperties: false,
204223
};
205224

225+
const ChatPostMessageSchema = {
226+
oneOf: [
227+
{
228+
type: 'object',
229+
properties: {
230+
roomId: {
231+
oneOf: [
232+
{ type: 'string' },
233+
{
234+
type: 'array',
235+
items: {
236+
type: 'string',
237+
},
238+
},
239+
],
240+
},
241+
text: {
242+
type: 'string',
243+
nullable: true,
244+
},
245+
alias: {
246+
type: 'string',
247+
nullable: true,
248+
},
249+
emoji: {
250+
type: 'string',
251+
nullable: true,
252+
},
253+
avatar: {
254+
type: 'string',
255+
nullable: true,
256+
},
257+
attachments: {
258+
type: 'array',
259+
items: {
260+
type: 'object',
261+
},
262+
nullable: true,
263+
},
264+
tmid: {
265+
type: 'string',
266+
},
267+
customFields: {
268+
type: 'object',
269+
nullable: true,
270+
},
271+
parseUrls: {
272+
type: 'boolean',
273+
},
274+
},
275+
required: ['roomId'],
276+
additionalProperties: false,
277+
},
278+
{
279+
type: 'object',
280+
properties: {
281+
channel: {
282+
oneOf: [
283+
{ type: 'string' },
284+
{
285+
type: 'array',
286+
items: {
287+
type: 'string',
288+
},
289+
},
290+
],
291+
},
292+
text: {
293+
type: 'string',
294+
nullable: true,
295+
},
296+
alias: {
297+
type: 'string',
298+
nullable: true,
299+
},
300+
emoji: {
301+
type: 'string',
302+
nullable: true,
303+
},
304+
avatar: {
305+
type: 'string',
306+
nullable: true,
307+
},
308+
attachments: {
309+
type: 'array',
310+
items: {
311+
type: 'object',
312+
},
313+
nullable: true,
314+
},
315+
customFields: {
316+
type: 'object',
317+
nullable: true,
318+
},
319+
parseUrls: {
320+
type: 'boolean',
321+
},
322+
},
323+
required: ['channel'],
324+
additionalProperties: false,
325+
},
326+
],
327+
};
328+
206329
const isChatPinMessageProps = ajv.compile<ChatPinMessage>(ChatPinMessageSchema);
207330

208331
const isChatUnpinMessageProps = ajv.compile<ChatUnpinMessage>(ChatUnpinMessageSchema);
209332

333+
const isChatPostMessageProps = ajv.compile<ChatPostMessage>(ChatPostMessageSchema);
334+
210335
const chatEndpoints = API.v1
211336
.post(
212337
'chat.pinMessage',
@@ -350,13 +475,37 @@ const chatEndpoints = API.v1
350475
message,
351476
});
352477
},
353-
);
354-
355-
API.v1.addRoute(
356-
'chat.postMessage',
357-
{ authRequired: true, validateParams: isChatPostMessageProps },
358-
{
359-
async post() {
478+
)
479+
.post(
480+
'chat.postMessage',
481+
{
482+
authRequired: true,
483+
body: isChatPostMessageProps,
484+
response: {
485+
400: validateBadRequestErrorResponse,
486+
401: validateUnauthorizedErrorResponse,
487+
200: ajv.compile<{
488+
ts: number;
489+
channel: string;
490+
message: IMessage;
491+
}>({
492+
type: 'object',
493+
properties: {
494+
ts: { type: 'number' },
495+
channel: { type: 'string' },
496+
message: { $ref: '#/components/schemas/IMessage' },
497+
success: {
498+
type: 'boolean',
499+
enum: [true],
500+
description: 'Indicates if the request was successful.',
501+
},
502+
},
503+
required: ['ts', 'channel', 'message', 'success'],
504+
additionalProperties: false,
505+
}),
506+
},
507+
},
508+
async function action() {
360509
const { text, attachments } = this.bodyParams;
361510
const maxAllowedSize = settings.get<number>('Message_MaxAllowedSize') ?? 0;
362511

@@ -372,7 +521,15 @@ API.v1.addRoute(
372521
}
373522
}
374523

375-
const messageReturn = (await applyAirGappedRestrictionsValidation(() => processWebhookMessage(this.bodyParams, this.user)))[0];
524+
if (!this.user.username) {
525+
return API.v1.failure('Invalid user');
526+
}
527+
528+
const messageReturn = (
529+
await applyAirGappedRestrictionsValidation(() =>
530+
processWebhookMessage({ ...this.bodyParams, separateResponse: true }, this.user as RequiredField<IUser, 'username'>),
531+
)
532+
)[0];
376533

377534
if (!messageReturn?.message) {
378535
return API.v1.failure('unknown-error');
@@ -386,8 +543,7 @@ API.v1.addRoute(
386543
message,
387544
});
388545
},
389-
},
390-
);
546+
);
391547

392548
API.v1.addRoute(
393549
'chat.search',

packages/rest-typings/src/v1/chat.ts

Lines changed: 1 addition & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { IMessage, IRoom, MessageAttachment, IReadReceiptWithUser, MessageUrl, IThreadMainMessage } from '@rocket.chat/core-typings';
1+
import type { IMessage, IRoom, IReadReceiptWithUser, MessageUrl, IThreadMainMessage } from '@rocket.chat/core-typings';
22

33
import { ajv } from './Ajv';
44
import type { PaginatedRequest } from '../helpers/PaginatedRequest';
@@ -809,132 +809,6 @@ const ChatGetDeletedMessagesSchema = {
809809

810810
export const isChatGetDeletedMessagesProps = ajv.compile<ChatGetDeletedMessages>(ChatGetDeletedMessagesSchema);
811811

812-
type ChatPostMessage =
813-
| {
814-
roomId: string | string[];
815-
text?: string;
816-
alias?: string;
817-
emoji?: string;
818-
avatar?: string;
819-
attachments?: MessageAttachment[];
820-
customFields?: IMessage['customFields'];
821-
}
822-
| {
823-
channel: string | string[];
824-
text?: string;
825-
alias?: string;
826-
emoji?: string;
827-
avatar?: string;
828-
attachments?: MessageAttachment[];
829-
customFields?: IMessage['customFields'];
830-
};
831-
832-
const ChatPostMessageSchema = {
833-
oneOf: [
834-
{
835-
type: 'object',
836-
properties: {
837-
roomId: {
838-
oneOf: [
839-
{ type: 'string' },
840-
{
841-
type: 'array',
842-
items: {
843-
type: 'string',
844-
},
845-
},
846-
],
847-
},
848-
text: {
849-
type: 'string',
850-
nullable: true,
851-
},
852-
alias: {
853-
type: 'string',
854-
nullable: true,
855-
},
856-
emoji: {
857-
type: 'string',
858-
nullable: true,
859-
},
860-
avatar: {
861-
type: 'string',
862-
nullable: true,
863-
},
864-
attachments: {
865-
type: 'array',
866-
items: {
867-
type: 'object',
868-
},
869-
nullable: true,
870-
},
871-
tmid: {
872-
type: 'string',
873-
},
874-
customFields: {
875-
type: 'object',
876-
nullable: true,
877-
},
878-
parseUrls: {
879-
type: 'boolean',
880-
},
881-
},
882-
required: ['roomId'],
883-
additionalProperties: false,
884-
},
885-
{
886-
type: 'object',
887-
properties: {
888-
channel: {
889-
oneOf: [
890-
{ type: 'string' },
891-
{
892-
type: 'array',
893-
items: {
894-
type: 'string',
895-
},
896-
},
897-
],
898-
},
899-
text: {
900-
type: 'string',
901-
nullable: true,
902-
},
903-
alias: {
904-
type: 'string',
905-
nullable: true,
906-
},
907-
emoji: {
908-
type: 'string',
909-
nullable: true,
910-
},
911-
avatar: {
912-
type: 'string',
913-
nullable: true,
914-
},
915-
attachments: {
916-
type: 'array',
917-
items: {
918-
type: 'object',
919-
},
920-
nullable: true,
921-
},
922-
customFields: {
923-
type: 'object',
924-
nullable: true,
925-
},
926-
parseUrls: {
927-
type: 'boolean',
928-
},
929-
},
930-
required: ['channel'],
931-
additionalProperties: false,
932-
},
933-
],
934-
};
935-
936-
export const isChatPostMessageProps = ajv.compile<ChatPostMessage>(ChatPostMessageSchema);
937-
938812
type ChatGetURLPreview = {
939813
roomId: IRoom['_id'];
940814
url: string;
@@ -1064,13 +938,6 @@ export type ChatEndpoints = {
1064938
};
1065939
};
1066940
};
1067-
'/v1/chat.postMessage': {
1068-
POST: (params: ChatPostMessage) => {
1069-
ts: number;
1070-
channel: IRoom;
1071-
message: IMessage;
1072-
};
1073-
};
1074941
'/v1/chat.syncThreadMessages': {
1075942
GET: (params: ChatSyncThreadMessages) => {
1076943
messages: {

0 commit comments

Comments
 (0)