Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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/migrate-chat-getThreadMessages-to-openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/meteor': minor
'@rocket.chat/rest-typings': minor
---

Migrated chat.getThreadMessages endpoint to new OpenAPI pattern with AJV validation
154 changes: 108 additions & 46 deletions apps/meteor/app/api/server/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
isChatReactProps,
isChatGetDeletedMessagesProps,
isChatSyncThreadsListProps,
isChatGetThreadMessagesProps,
isChatSyncThreadMessagesProps,
isChatGetStarredMessagesProps,
isChatGetDiscussionsProps,
Expand Down Expand Up @@ -275,6 +274,47 @@ const isChatPinMessageProps = ajv.compile<ChatPinMessage>(ChatPinMessageSchema);

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

type ChatGetThreadMessages = {
tmid: string;
count?: number;
offset?: number;
sort?: string;
};

const ChatGetThreadMessagesSchema = {
type: 'object',
properties: {
tmid: {
type: 'string',
minLength: 1,
},
count: {
type: 'number',
nullable: true,
},
offset: {
type: 'number',
nullable: true,
},
sort: {
type: 'string',
nullable: true,
},
query: {
type: 'string',
nullable: true,
},
fields: {
type: 'string',
nullable: true,
},
},
required: ['tmid'],
additionalProperties: false,
};

const isChatGetThreadMessagesLocalProps = ajv.compile<ChatGetThreadMessages>(ChatGetThreadMessagesSchema);

const chatEndpoints = API.v1
.post(
'chat.pinMessage',
Expand Down Expand Up @@ -558,6 +598,73 @@ const chatEndpoints = API.v1

return API.v1.success();
},
)
.get(
'chat.getThreadMessages',
{
authRequired: true,
query: isChatGetThreadMessagesLocalProps,
response: {
200: ajv.compile<{
messages: IMessage[];
count: number;
offset: number;
total: number;
success: boolean;
}>({
type: 'object',
properties: {
messages: { type: 'array' },
count: { type: 'number' },
offset: { type: 'number' },
total: { type: 'number' },
success: { type: 'boolean', enum: [true] },
},
required: ['messages', 'count', 'offset', 'total', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { tmid } = this.queryParams;
const { query, fields, sort } = await this.parseJsonQuery();
const { offset, count } = await getPaginationItems(this.queryParams);

if (!settings.get('Threads_enabled')) {
throw new Meteor.Error('error-not-allowed', 'Threads Disabled');
}

const thread = await Messages.findOneById(tmid, { projection: { rid: 1 } });
if (!thread?.rid) {
throw new Meteor.Error('error-invalid-message', 'Invalid Message');
}
const user = await Users.findOneById(this.userId, { projection: { _id: 1 } });
const room = await Rooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } });

if (!room || !user || !(await canAccessRoomAsync(room, user))) {
throw new Meteor.Error('error-not-allowed', 'Not Allowed');
}
const { cursor, totalCount } = Messages.findPaginated(
{ ...query, tmid },
{
sort: sort || { ts: 1 },
skip: offset,
limit: count,
projection: fields,
},
);

const [messages, total] = await Promise.all([cursor.toArray(), totalCount]);

return API.v1.success({
messages,
count: messages.length,
offset,
total,
});
},
);

API.v1.addRoute(
Expand Down Expand Up @@ -873,51 +980,6 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'chat.getThreadMessages',
{ authRequired: true, validateParams: isChatGetThreadMessagesProps },
{
async get() {
const { tmid } = this.queryParams;
const { query, fields, sort } = await this.parseJsonQuery();
const { offset, count } = await getPaginationItems(this.queryParams);

if (!settings.get('Threads_enabled')) {
throw new Meteor.Error('error-not-allowed', 'Threads Disabled');
}

const thread = await Messages.findOneById(tmid, { projection: { rid: 1 } });
if (!thread?.rid) {
throw new Meteor.Error('error-invalid-message', 'Invalid Message');
}
const user = await Users.findOneById(this.userId, { projection: { _id: 1 } });
const room = await Rooms.findOneById(thread.rid, { projection: { ...roomAccessAttributes, t: 1, _id: 1 } });

if (!room || !user || !(await canAccessRoomAsync(room, user))) {
throw new Meteor.Error('error-not-allowed', 'Not Allowed');
}
const { cursor, totalCount } = Messages.findPaginated(
{ ...query, tmid },
{
sort: sort || { ts: 1 },
skip: offset,
limit: count,
projection: fields,
},
);

const [messages, total] = await Promise.all([cursor.toArray(), totalCount]);

return API.v1.success({
messages,
count: messages.length,
offset,
total,
});
},
},
);

API.v1.addRoute(
'chat.syncThreadMessages',
{ authRequired: true, validateParams: isChatSyncThreadMessagesProps },
Expand Down
38 changes: 0 additions & 38 deletions packages/rest-typings/src/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,36 +673,6 @@ const ChatSyncThreadMessagesSchema = {

export const isChatSyncThreadMessagesProps = ajv.compile<ChatSyncThreadMessages>(ChatSyncThreadMessagesSchema);

type ChatGetThreadMessages = PaginatedRequest<{
tmid: string;
}>;

const ChatGetThreadMessagesSchema = {
type: 'object',
properties: {
tmid: {
type: 'string',
minLength: 1,
},
count: {
type: 'number',
nullable: true,
},
offset: {
type: 'number',
nullable: true,
},
sort: {
type: 'string',
nullable: true,
},
},
required: ['tmid'],
additionalProperties: false,
};

export const isChatGetThreadMessagesProps = ajv.compile<ChatGetThreadMessages>(ChatGetThreadMessagesSchema);

type ChatGetDeletedMessages = PaginatedRequest<{
roomId: IRoom['_id'];
since: string;
Expand Down Expand Up @@ -997,14 +967,6 @@ export type ChatEndpoints = {
};
};
};
'/v1/chat.getThreadMessages': {
GET: (params: ChatGetThreadMessages) => {
messages: IMessage[];
count: number;
offset: number;
total: number;
};
};
'/v1/chat.getDeletedMessages': {
GET: (params: ChatGetDeletedMessages) => {
messages: IMessage[];
Expand Down