Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5f1de18
refactor: remove ChatPostMessageEndpoints to Endpoints
ahmed-n-abdeltwab Jun 21, 2025
dbfd71b
refactor: return to the old implementation to ensure zero changes.
ahmed-n-abdeltwab Jun 20, 2025
2d2291a
refactor: fix the ci error
ahmed-n-abdeltwab Jun 20, 2025
59083a9
Create healthy-dolls-pretend.md
ahmed-n-abdeltwab Jun 19, 2025
23fd12b
refactor: remove unused IRoom type import from chat.ts
ahmed-n-abdeltwab Jun 19, 2025
d8aed6e
refactor: fix lint
ahmed-n-abdeltwab Jun 19, 2025
1a351bd
refactor: rename ChatEndpoint interface to IChatPostMessageEndpoints …
ahmed-n-abdeltwab Jun 19, 2025
546d6b3
refactor: update the chat.postMessage response and use the ExtractRou…
ahmed-n-abdeltwab Jun 19, 2025
e8c0762
refactor: update ChatPostMessage type and schema for improved flexibi…
ahmed-n-abdeltwab Jun 16, 2025
d59d751
refactor: remove unused MessageAttachment import from chat.ts
ahmed-n-abdeltwab Jun 9, 2025
6a3a3ab
fix: remove validateParams from chat.postMessage route
ahmed-n-abdeltwab Jun 9, 2025
b1e617b
refactor: remove ChatPostMessage type and schema from chat.ts
ahmed-n-abdeltwab Jun 9, 2025
864b7d1
refactor: add ChatPostMessage type and schema for chat.postMessage API
ahmed-n-abdeltwab Jun 9, 2025
394d687
refactor: add the removed ChatPostMessage so i will remove it later
ahmed-n-abdeltwab Jun 26, 2025
19f1f9a
refactor: Add the variable API.v1.post to the chatPostMessageEndpoints.
ahmed-n-abdeltwab Jun 26, 2025
e336a45
refactor: Update chat.postMessage endpoint to include validateParams …
ahmed-n-abdeltwab Jun 26, 2025
fd7dc58
Merge branch 'RocketChat:develop' into feat/openapi-chat-postMessage
ahmed-n-abdeltwab Jul 11, 2025
3c69a97
Merge branch 'develop' into feat/openapi-chat-postMessage
ahmed-n-abdeltwab Jul 11, 2025
5f706ae
Merge branch 'develop' into feat/openapi-chat-postMessage
cardoso Jul 14, 2025
bad8bc8
Merge branch 'develop' into feat/openapi-chat-postMessage
cardoso Jul 15, 2025
144909f
Merge branch 'develop' into feat/openapi-chat-postMessage
ahmed-n-abdeltwab Jul 15, 2025
dfd44cb
Merge branch 'develop' into feat/openapi-chat-postMessage
cardoso Jul 15, 2025
b68c28d
fix: remove the chat.postMessage type from ChatEndpoints
ahmed-n-abdeltwab Jul 16, 2025
d940827
refactor: improve the error schema
ahmed-n-abdeltwab Jul 16, 2025
d3f4525
refactor: fix the chat post schema and add a debug prints
ahmed-n-abdeltwab Jul 16, 2025
15e5231
refactor: reomve the unused props
ahmed-n-abdeltwab Jul 16, 2025
4edd60c
refactor: remove the declaration isObject
ahmed-n-abdeltwab Jul 16, 2025
2cf92bf
refactor: remove unimportant null from the json schema
ahmed-n-abdeltwab Jul 16, 2025
49a7dda
refactor: remove unused import
ahmed-n-abdeltwab Jul 16, 2025
aa1b7be
chore: fix some lints
ahmed-n-abdeltwab Jul 16, 2025
da22eef
Merge branch 'develop' into feat/openapi-chat-postMessage
ahmed-n-abdeltwab Sep 4, 2025
d94a574
Merge branch 'develop' into feat/openapi-chat-postMessage
ahmed-n-abdeltwab Sep 27, 2025
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/healthy-dolls-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/rest-typings": patch
---

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.
240 changes: 208 additions & 32 deletions apps/meteor/app/api/server/v1/chat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Message } from '@rocket.chat/core-services';
import type { IMessage, IThreadMainMessage } from '@rocket.chat/core-typings';
import type { IMessage, IUser, IThreadMainMessage, MessageAttachment, RequiredField } from '@rocket.chat/core-typings';
import { MessageTypes } from '@rocket.chat/message-types';
import { Messages, Users, Rooms, Subscriptions } from '@rocket.chat/models';
import {
Expand All @@ -11,7 +11,6 @@
isChatDeleteProps,
isChatSyncMessagesProps,
isChatGetMessageProps,
isChatPostMessageProps,
isChatSearchProps,
isChatSendMessageProps,
isChatStarMessageProps,
Expand All @@ -32,6 +31,7 @@
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';
import { ajv } from '@rocket.chat/rest-typings/src/v1/Ajv';

Check failure on line 34 in apps/meteor/app/api/server/v1/chat.ts

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

'ajv' is already defined
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Meteor } from 'meteor/meteor';

Expand Down Expand Up @@ -284,43 +284,219 @@
},
);

API.v1.addRoute(
'chat.postMessage',
{ authRequired: true, validateParams: isChatPostMessageProps },
{
async post() {
const { text, attachments } = this.bodyParams;
const maxAllowedSize = settings.get<number>('Message_MaxAllowedSize') ?? 0;
type ChatPostMessage =
| {
roomId: string | string[];
text?: string;
alias?: string;
emoji?: string;
avatar?: string;
attachments?: MessageAttachment[];
customFields?: IMessage['customFields'];
}
| {
channel: string | string[];
text?: string;
alias?: string;
emoji?: string;
avatar?: string;
attachments?: MessageAttachment[];
customFields?: IMessage['customFields'];
};

const ChatPostMessageSchema = {
type: 'object',
properties: {
roomId: {
oneOf: [
{ type: 'string' },
{
type: 'array',
items: { type: 'string' },
},
],
},
channel: {
oneOf: [
{ type: 'string' },
{
type: 'array',
items: { type: 'string' },
},
],
},
text: { type: 'string' },
alias: { type: 'string' },
emoji: { type: 'string' },
avatar: { type: 'string' },
attachments: {
type: 'array',
items: { type: 'object' },
},
customFields: { type: 'object' },
},
additionalProperties: false,
required: [],
};

if (text && text.length > maxAllowedSize) {
return API.v1.failure('error-message-size-exceeded');
}
const isChatPostMessageProps = ajv.compile<ChatPostMessage>(ChatPostMessageSchema);

if (attachments && attachments.length > 0) {
for (const attachment of attachments) {
if (attachment.text && attachment.text.length > maxAllowedSize) {
return API.v1.failure('error-message-size-exceeded');
}
const chatPostMessageEndpoints = API.v1.post(
'chat.postMessage',
{
authRequired: true,
body: isChatPostMessageProps,
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<{
ts: number;
channel: string;
message: IMessage;
success: boolean;
}>({
type: 'object',
properties: {
ts: { type: 'number' },
channel: { type: 'string' },
message: {
// ? Accepts any type for message
type: 'object',
properties: {
alias: { type: 'string' },
msg: { type: 'string' },
attachments: { type: 'array' },
parseUrls: { type: 'boolean' },
groupable: { type: 'boolean' },
// ? Set this as a string type with date-time as a format, instead of using an object type
ts: {
oneOf: [{ type: 'string', format: 'date-time' }, { type: 'object' }],
},
u: {
type: 'object',
properties: {
_id: { type: 'string' },
name: { type: 'string' },
username: { type: 'string' },
},
required: ['_id', 'name', 'username'],
additionalProperties: false,
},
rid: { type: 'string' },
_id: { type: 'string' },
// ? Set this as a string type with date-time as a format, instead of using an object type
_updatedAt: {
oneOf: [{ type: 'string', format: 'date-time' }, { type: 'object' }],
},
urls: { type: 'array', items: { type: 'string' } },
mentions: {
type: 'array',
items: {
type: 'object',
},
},
channels: {
type: 'array',
items: {
type: 'object',
},
},
md: { type: 'array' },
tmid: { type: 'string' },
tshow: { type: 'boolean' },
emoji: { type: 'string' },
// ? Should customFields have properties implement in the IMessageCustomFields interface and ensure it is not empty?
customFields: {
type: 'object',
properties: {},
additionalProperties: true,
},
},
required: ['msg', '_updatedAt'],
additionalProperties: false,
},
success: {
type: 'boolean',
enum: [true],
description: 'Indicates if the request was successful.',
},
},
required: ['ts', 'channel', 'message', 'success'],
additionalProperties: false,
}),
},
},
async function action() {
const { text, attachments } = this.bodyParams;
const maxAllowedSize = settings.get<number>('Message_MaxAllowedSize') ?? 0;

if (text && text.length > maxAllowedSize) {
return API.v1.failure('error-message-size-exceeded');
}

if (attachments && attachments.length > 0) {
for (const attachment of attachments) {
if (attachment.text && attachment.text.length > maxAllowedSize) {
return API.v1.failure('error-message-size-exceeded');
}
}

const messageReturn = (await applyAirGappedRestrictionsValidation(() => processWebhookMessage(this.bodyParams, this.user)))[0];

if (!messageReturn?.message) {
return API.v1.failure('unknown-error');
}

const [message] = await normalizeMessagesForUser([messageReturn.message], this.userId);

return API.v1.success({
ts: Date.now(),
channel: messageReturn.channel,
message,
});
},
}
console.log('this:', this.bodyParams);
const messageReturn = (
await applyAirGappedRestrictionsValidation(() =>
processWebhookMessage(this.bodyParams, this.user as IUser & { username: RequiredField<IUser, 'username'> }),
)
)[0];
console.log('messageReturn:', messageReturn);
if (!messageReturn) {
return API.v1.failure('unknown-error');
}

const [message] = (await normalizeMessagesForUser([messageReturn.message], this.userId)) as IMessage[];
console.log('message:', message);
return API.v1.success({
ts: Date.now(),
channel: messageReturn.channel,
message,
});
},
);

export type ChatPostMessageEndpoints = ExtractRoutesFromAPI<typeof chatPostMessageEndpoints>;

// TODO: Need to remove the ChatEndpoints packages/rest-typings/src/index.ts file, but only after implementing all the endpoints.
declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends ChatPostMessageEndpoints {}
}

API.v1.addRoute(
'chat.search',
{ authRequired: true, validateParams: isChatSearchProps },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Room } from '@rocket.chat/core-services';
import type { IRoom, IUser, RoomType } from '@rocket.chat/core-typings';
import { Rooms, Users } from '@rocket.chat/models';
import { isObject } from '@rocket.chat/tools';
import { Meteor } from 'meteor/meteor';

import { isObject } from '../../../../lib/utils/isObject';
import { createDirectMessage } from '../../../../server/methods/createDirectMessage';

export const getRoomByNameOrIdWithOptionToJoin = async ({
Expand Down
Loading
Loading