Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
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/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.
85 changes: 57 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,45 @@ 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() {
const msg: IMessage | null = await getSingleMessage(this.userId, this.queryParams.msgId);

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 @@ -76,6 +77,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,
};
});
}

Comment on lines +80 to +100
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this required?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The short answer is yes. The reason this fixed the problem is that the editedMessage attachments were missing. This caused the test to fail because the JSON schema was able to detect the missing data

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It happens a lot with other APIs too. I'm going to try my best to avoid making any changes, especially to the frontend or the database

// 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