From 0d4b946f32473ee3266914fc621feaadb773df9d Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Sat, 21 Feb 2026 13:47:02 +0200 Subject: [PATCH 1/8] Add OpenAPI support for the Rocket.Chat rooms.favorite APIs 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. --- .changeset/tricky-boxes-type.md | 6 ++ apps/meteor/app/api/server/v1/rooms.ts | 105 ++++++++++++++++++------- packages/rest-typings/src/v1/rooms.ts | 14 ---- 3 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 .changeset/tricky-boxes-type.md diff --git a/.changeset/tricky-boxes-type.md b/.changeset/tricky-boxes-type.md new file mode 100644 index 0000000000000..084f3f79fe242 --- /dev/null +++ b/.changeset/tricky-boxes-type.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat rooms.favorite APIs 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. diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 686f76a7476c6..614acc85beefd 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -311,26 +311,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'rooms.favorite', - { authRequired: true }, - { - async post() { - const { favorite } = this.bodyParams; - - if (!this.bodyParams.hasOwnProperty('favorite')) { - return API.v1.failure("The 'favorite' param is required"); - } - - const room = await findRoomByIdOrName({ params: this.bodyParams }); - - await toggleFavoriteMethod(this.userId, room._id, favorite); - - return API.v1.success(); - }, - }, -); - API.v1.addRoute( 'rooms.cleanHistory', { authRequired: true, validateParams: isRoomsCleanHistoryProps }, @@ -945,6 +925,16 @@ API.v1.addRoute( }, ); +type RoomsFavorite = + | { + roomId: string; + favorite: boolean; + } + | { + roomName: string; + favorite: boolean; + }; + const isRoomGetRolesPropsSchema = { type: 'object', properties: { @@ -953,6 +943,32 @@ const isRoomGetRolesPropsSchema = { additionalProperties: false, required: ['rid'], }; + +const RoomsFavoriteSchema = { + anyOf: [ + { + type: 'object', + properties: { + favorite: { type: 'boolean' }, + roomName: { type: 'string' }, + }, + required: ['roomName', 'favorite'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + favorite: { type: 'boolean' }, + roomId: { type: 'string' }, + }, + required: ['roomId', 'favorite'], + additionalProperties: false, + }, + ], +}; + +const isRoomsFavoriteProps = ajv.compile(RoomsFavoriteSchema); + export const roomEndpoints = API.v1 .get( 'rooms.roles', @@ -1066,9 +1082,8 @@ export const roomEndpoints = API.v1 total, }); }, - ); - -const roomInviteEndpoints = API.v1.post( + ) + .post( 'rooms.invite', { authRequired: true, @@ -1089,16 +1104,52 @@ const roomInviteEndpoints = API.v1.post( async function action() { const { roomId, action } = this.bodyParams; - try { + try { await FederationMatrix.handleInvite(roomId, this.userId, action); return API.v1.success(); } catch (error) { return API.v1.failure({ error: `Failed to handle invite: ${error instanceof Error ? error.message : String(error)}` }); } - }, -); + + }, + ) + .post( + 'rooms.favorite', + { + authRequired: true, + body: isRoomsFavoriteProps, + response: { + 200: ajv.compile({ + type: 'object', + properties: { + success: { + type: 'boolean', + description: 'Indicates if the request was successful.', + }, + }, + required: ['success'], + additionalProperties: false, + }), + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + }, + }, + async function action() { + const { favorite } = this.bodyParams; + + if (!this.bodyParams.hasOwnProperty('favorite')) { + return API.v1.failure("The 'favorite' param is required"); + } + + const room = await findRoomByIdOrName({ params: this.bodyParams }); + + await toggleFavoriteMethod(this.userId, room._id, favorite); + + return API.v1.success(); + }, + ); -type RoomEndpoints = ExtractRoutesFromAPI & ExtractRoutesFromAPI; +type RoomEndpoints = ExtractRoutesFromAPI; declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index c8d77253ecf5c..18c4574f7bb23 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -805,20 +805,6 @@ export type RoomsEndpoints = { POST: (params: { roomId: string; notifications: Notifications }) => void; }; - '/v1/rooms.favorite': { - POST: ( - params: - | { - roomId: string; - favorite: boolean; - } - | { - roomName: string; - favorite: boolean; - }, - ) => void; - }; - '/v1/rooms.nameExists': { GET: (params: { roomName: string }) => { exists: boolean; From 2a258f1919803fd3611f32c46a370e045471785a Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Sat, 21 Feb 2026 14:01:05 +0200 Subject: [PATCH 2/8] fix: the file format --- apps/meteor/app/api/server/v1/rooms.ts | 47 +++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 614acc85beefd..926fe5148a698 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1084,33 +1084,32 @@ export const roomEndpoints = API.v1 }, ) .post( - 'rooms.invite', - { - authRequired: true, - body: isRoomsInviteProps, - response: { - 400: validateBadRequestErrorResponse, - 401: validateUnauthorizedErrorResponse, - 200: ajv.compile({ - type: 'object', - properties: { - success: { type: 'boolean', enum: [true] }, - }, - required: ['success'], - additionalProperties: false, - }), + 'rooms.invite', + { + authRequired: true, + body: isRoomsInviteProps, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 200: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [true] }, + }, + required: ['success'], + additionalProperties: false, + }), + }, }, - }, - async function action() { - const { roomId, action } = this.bodyParams; + async function action() { + const { roomId, action } = this.bodyParams; try { - await FederationMatrix.handleInvite(roomId, this.userId, action); - return API.v1.success(); - } catch (error) { - return API.v1.failure({ error: `Failed to handle invite: ${error instanceof Error ? error.message : String(error)}` }); - } - + await FederationMatrix.handleInvite(roomId, this.userId, action); + return API.v1.success(); + } catch (error) { + return API.v1.failure({ error: `Failed to handle invite: ${error instanceof Error ? error.message : String(error)}` }); + } }, ) .post( From 6acc3d612ef9d1b8cba75ad2b92f39e9c46e44bc Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Sat, 21 Feb 2026 14:03:02 +0200 Subject: [PATCH 3/8] chore: add enum: [true] in the success schema --- apps/meteor/app/api/server/v1/rooms.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 926fe5148a698..2178761194515 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1123,6 +1123,7 @@ export const roomEndpoints = API.v1 properties: { success: { type: 'boolean', + enum: [true], description: 'Indicates if the request was successful.', }, }, From 051932b2fd1bc8599bcc051b5c3285ed2133ea6b Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Mon, 23 Feb 2026 19:06:24 +0200 Subject: [PATCH 4/8] fix(rooms): solve coderabbitai issues error handling and remove redundant validation --- apps/meteor/app/api/server/v1/rooms.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 2178761194515..6518559aea2f2 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1108,7 +1108,7 @@ export const roomEndpoints = API.v1 await FederationMatrix.handleInvite(roomId, this.userId, action); return API.v1.success(); } catch (error) { - return API.v1.failure({ error: `Failed to handle invite: ${error instanceof Error ? error.message : String(error)}` }); + throw new Meteor.Error(`Failed to handle invite: ${error instanceof Error ? error.message : String(error)}`); } }, ) @@ -1137,10 +1137,6 @@ export const roomEndpoints = API.v1 async function action() { const { favorite } = this.bodyParams; - if (!this.bodyParams.hasOwnProperty('favorite')) { - return API.v1.failure("The 'favorite' param is required"); - } - const room = await findRoomByIdOrName({ params: this.bodyParams }); await toggleFavoriteMethod(this.userId, room._id, favorite); From b8d9007eb502a64b557acbdb0fd8929fbce16866 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 23 Feb 2026 14:54:31 -0300 Subject: [PATCH 5/8] Apply suggestion from @ggazzo --- apps/meteor/app/api/server/v1/rooms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 6518559aea2f2..988e60002eef9 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1108,7 +1108,7 @@ export const roomEndpoints = API.v1 await FederationMatrix.handleInvite(roomId, this.userId, action); return API.v1.success(); } catch (error) { - throw new Meteor.Error(`Failed to handle invite: ${error instanceof Error ? error.message : String(error)}`); + return API.v1.failure({ error: `Failed to handle invite: ${error instanceof Error ? error.message : String(error)}` }); } }, ) From 0ecf65741a9a9e495d160d852e8f5deb63055e5c Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 23 Feb 2026 15:25:30 -0300 Subject: [PATCH 6/8] Apply suggestion from @ggazzo --- apps/meteor/app/api/server/v1/rooms.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 988e60002eef9..503e301c11c99 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1118,7 +1118,11 @@ export const roomEndpoints = API.v1 authRequired: true, body: isRoomsFavoriteProps, response: { - 200: ajv.compile({ + 200: ajv.compile< + { + favorite: boolean; + } & ({ roomId: string } | { roomName: string }) + >({ type: 'object', properties: { success: { From fa25b50e62b927b8cdb7b09204338d579b659421 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 23 Feb 2026 15:27:05 -0300 Subject: [PATCH 7/8] lint --- apps/meteor/app/api/server/v1/rooms.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 503e301c11c99..14ed43f4b92ce 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1119,10 +1119,10 @@ export const roomEndpoints = API.v1 body: isRoomsFavoriteProps, response: { 200: ajv.compile< - { - favorite: boolean; - } & ({ roomId: string } | { roomName: string }) - >({ + { + favorite: boolean; + } & ({ roomId: string } | { roomName: string }) + >({ type: 'object', properties: { success: { From 2186c0bf16ca5ff585ad11d90a5e09b9cd7fd7fd Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 23 Feb 2026 15:49:43 -0300 Subject: [PATCH 8/8] Apply suggestion from @ggazzo --- apps/meteor/app/api/server/v1/rooms.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 14ed43f4b92ce..988e60002eef9 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1118,11 +1118,7 @@ export const roomEndpoints = API.v1 authRequired: true, body: isRoomsFavoriteProps, response: { - 200: ajv.compile< - { - favorite: boolean; - } & ({ roomId: string } | { roomName: string }) - >({ + 200: ajv.compile({ type: 'object', properties: { success: {