Skip to content
6 changes: 6 additions & 0 deletions .changeset/unlucky-sloths-cough.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.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.
87 changes: 59 additions & 28 deletions apps/meteor/app/api/server/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
isChatGetThreadsListProps,
isChatDeleteProps,
isChatSyncMessagesProps,
isChatGetMessageProps,
isChatPostMessageProps,
isChatSearchProps,
isChatSendMessageProps,
Expand Down Expand Up @@ -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'];
};
Expand All @@ -179,6 +151,10 @@ type ChatUnpinMessage = {
messageId: IMessage['_id'];
};

type ChatGetMessage = {
msgId: IMessage['_id'];
};

const ChatPinMessageSchema = {
type: 'object',
properties: {
Expand All @@ -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<ChatPinMessage>(ChatPinMessageSchema);

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

const isChatGetMessageProps = ajv.compile<ChatGetMessage>(ChatGetMessageSchema);

const chatEndpoints = API.v1
.post(
'chat.pinMessage',
Expand Down Expand Up @@ -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,
});
Expand Down
22 changes: 22 additions & 0 deletions apps/meteor/app/lib/server/functions/updateMessage.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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 },
Expand Down
23 changes: 0 additions & 23 deletions packages/rest-typings/src/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,6 @@ const chatUnfollowMessageSchema = {

export const isChatUnfollowMessageProps = ajv.compile<ChatUnfollowMessage>(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<ChatGetMessage>(ChatGetMessageSchema);

type ChatStarMessage = {
messageId: IMessage['_id'];
};
Expand Down Expand Up @@ -962,11 +944,6 @@ export type ChatEndpoints = {
message: IMessage;
};
};
'/v1/chat.getMessage': {
GET: (params: ChatGetMessage) => {
message: IMessage;
};
};
'/v1/chat.followMessage': {
POST: (params: ChatFollowMessage) => void;
};
Expand Down
Loading