From 986dd9a1a972f7042fefffd3cb6a54b2b2cb44d1 Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Thu, 5 Mar 2026 09:45:20 +0000 Subject: [PATCH 1/8] Migration: groups.removeModerator to new API pattern --- apps/meteor/app/api/server/v1/groups.ts | 80 +++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 2437813860836..8941bf91b6ae1 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -1,7 +1,7 @@ import { Team, isMeteorError } from '@rocket.chat/core-services'; import type { IIntegration, IUser, IRoom, RoomType, UserStatus } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; -import { isGroupsOnlineProps, isGroupsMessagesProps, isGroupsFilesProps } from '@rocket.chat/rest-typings'; +import { isGroupsOnlineProps, isGroupsMessagesProps, isGroupsFilesProps, ajv } from '@rocket.chat/rest-typings'; import { isTruthy } from '@rocket.chat/tools'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -36,6 +36,9 @@ import { addUserToFileObj } from '../helpers/addUserToFileObj'; import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage'; import { getPaginationItems } from '../helpers/getPaginationItems'; import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFromParams'; +import { ExtractRoutesFromAPI } from '../ApiClass'; +import { validateBadRequestErrorResponse } from '@rocket.chat/rest-typings'; +import { validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings'; async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise { if ( @@ -891,11 +894,68 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'groups.removeModerator', - { authRequired: true }, + + + +type GroupsBaseProps = { roomId: string; roomName?: string } | { roomId?: string; roomName: string }; +const withGroupBaseProperties = (properties: Record = {}, required: string[] = []) => ({ + oneOf: [ + { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + ...properties, + }, + required: ['roomId'].concat(required), + additionalProperties: false, + }, + { + type: 'object', + properties: { + roomName: { + type: 'string', + }, + ...properties, + }, + required: ['roomName'].concat(required), + additionalProperties: false, + }, + ], +}); + +type WithUserId = GroupsBaseProps & { userId: string }; +const withUserIdSchema = withGroupBaseProperties( { - async post() { + userId: { + type: 'string', + }, + }, + ['userId'], +); +export const GroupsRemoveModeratorProps = ajv.compile(withUserIdSchema); + + +const groupsEndpoints = API.v1.post( + "groups.removeModerator", + { + authRequired:true, + body: GroupsRemoveModeratorProps, + response:{ + 400:validateBadRequestErrorResponse, + 401:validateUnauthorizedErrorResponse, + 200:ajv.compile({ + type:'object', + properties:{ + success:{type:'boolean', enum:[true]}, + }, + required:['success'], + additionalProperties:false, + }), + }, + }, + async function action() { const findResult = await findPrivateGroupByIdOrName({ params: this.bodyParams, userId: this.userId, @@ -906,8 +966,8 @@ API.v1.addRoute( await removeRoomModerator(this.userId, findResult.rid, user._id); return API.v1.success(); - }, }, + ); API.v1.addRoute( @@ -1300,3 +1360,11 @@ API.v1.addRoute( }, }, ); + + +export type GroupEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends GroupEndpoints {} +} \ No newline at end of file From 4387f34f4aa7508b5b55c31580401fc6a21a302e Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Thu, 5 Mar 2026 09:58:38 +0000 Subject: [PATCH 2/8] feat(api): add OpenAPI support for groups.removeModerator endpoint and migrate to modern route definition --- .changeset/groups.removeModerator.md | 5 +++++ .../rest-typings/src/v1/groups/GroupsRemoveModeratorProps.ts | 5 ----- packages/rest-typings/src/v1/groups/groups.ts | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 .changeset/groups.removeModerator.md delete mode 100644 packages/rest-typings/src/v1/groups/GroupsRemoveModeratorProps.ts diff --git a/.changeset/groups.removeModerator.md b/.changeset/groups.removeModerator.md new file mode 100644 index 0000000000000..a00137d2a8eb8 --- /dev/null +++ b/.changeset/groups.removeModerator.md @@ -0,0 +1,5 @@ +"@rocket.chat/meteor": minor +"@rocket.chat/rest-typings": minor +--- + +Add OpenAPI support for the Rocket.Chat groups.removeModerator API endpoints by migrating to a modern chained route definition syntax and utilizing shared AJV schemas for validation \ No newline at end of file diff --git a/packages/rest-typings/src/v1/groups/GroupsRemoveModeratorProps.ts b/packages/rest-typings/src/v1/groups/GroupsRemoveModeratorProps.ts deleted file mode 100644 index c9dbd069094bd..0000000000000 --- a/packages/rest-typings/src/v1/groups/GroupsRemoveModeratorProps.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { WithUserId } from './BaseProps'; -import { withUserIdProps } from './BaseProps'; - -export type GroupsRemoveModeratorProps = WithUserId; -export const isGroupsRemoveModeratorProps = withUserIdProps; diff --git a/packages/rest-typings/src/v1/groups/groups.ts b/packages/rest-typings/src/v1/groups/groups.ts index a7c937b08a9fc..ea4f20d92b94b 100644 --- a/packages/rest-typings/src/v1/groups/groups.ts +++ b/packages/rest-typings/src/v1/groups/groups.ts @@ -24,7 +24,6 @@ import type { GroupsModeratorsProps } from './GroupsModeratorsProps'; import type { GroupsOnlineProps } from './GroupsOnlineProps'; import type { GroupsOpenProps } from './GroupsOpenProps'; import type { GroupsRemoveLeaderProps } from './GroupsRemoveLeaderProps'; -import type { GroupsRemoveModeratorProps } from './GroupsRemoveModeratorProps'; import type { GroupsRemoveOwnerProps } from './GroupsRemoveOwnerProps'; import type { GroupsRenameProps } from './GroupsRenameProps'; import type { GroupsRolesProps } from './GroupsRolesProps'; @@ -107,9 +106,7 @@ export type GroupsEndpoints = { '/v1/groups.addModerator': { POST: (params: GroupsAddModeratorProps) => void; }; - '/v1/groups.removeModerator': { - POST: (params: GroupsRemoveModeratorProps) => void; - }; + '/v1/groups.addOwner': { POST: (params: GroupsAddOwnerProps) => void; }; From 53d19a1e359d150ff8cb639ada3f259464cf8fd0 Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Thu, 5 Mar 2026 10:25:20 +0000 Subject: [PATCH 3/8] refactor: reuse existing withUserIdProps instead of duplicating schema compilation --- apps/meteor/app/api/server/v1/groups.ts | 41 ++----------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 8941bf91b6ae1..fe8eb8c649b0f 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -39,6 +39,8 @@ import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFrom import { ExtractRoutesFromAPI } from '../ApiClass'; import { validateBadRequestErrorResponse } from '@rocket.chat/rest-typings'; import { validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings'; +import { withUserIdProps } from '@rocket.chat/rest-typings/dist/v1/groups/BaseProps'; + async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise { if ( @@ -897,51 +899,14 @@ API.v1.addRoute( -type GroupsBaseProps = { roomId: string; roomName?: string } | { roomId?: string; roomName: string }; -const withGroupBaseProperties = (properties: Record = {}, required: string[] = []) => ({ - oneOf: [ - { - type: 'object', - properties: { - roomId: { - type: 'string', - }, - ...properties, - }, - required: ['roomId'].concat(required), - additionalProperties: false, - }, - { - type: 'object', - properties: { - roomName: { - type: 'string', - }, - ...properties, - }, - required: ['roomName'].concat(required), - additionalProperties: false, - }, - ], -}); -type WithUserId = GroupsBaseProps & { userId: string }; -const withUserIdSchema = withGroupBaseProperties( - { - userId: { - type: 'string', - }, - }, - ['userId'], -); -export const GroupsRemoveModeratorProps = ajv.compile(withUserIdSchema); const groupsEndpoints = API.v1.post( "groups.removeModerator", { authRequired:true, - body: GroupsRemoveModeratorProps, + body: withUserIdProps, response:{ 400:validateBadRequestErrorResponse, 401:validateUnauthorizedErrorResponse, From d70599763782d17179d04d3f07624fa94b092d15 Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Fri, 6 Mar 2026 16:04:08 +0000 Subject: [PATCH 4/8] refactor: Remove GroupsRemoveModeratorProps and reformat the groups.removeModerator endpoint. --- apps/meteor/app/api/server/v1/groups.ts | 66 +++++++++----------- packages/rest-typings/src/v1/groups/index.ts | 1 - 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index fe8eb8c649b0f..0fd3c4cdf4a0e 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -1,7 +1,15 @@ import { Team, isMeteorError } from '@rocket.chat/core-services'; import type { IIntegration, IUser, IRoom, RoomType, UserStatus } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; -import { isGroupsOnlineProps, isGroupsMessagesProps, isGroupsFilesProps, ajv } from '@rocket.chat/rest-typings'; +import { + isGroupsOnlineProps, + isGroupsMessagesProps, + isGroupsFilesProps, + ajv, + validateBadRequestErrorResponse, + validateUnauthorizedErrorResponse, +} from '@rocket.chat/rest-typings'; +import { withUserIdProps } from '@rocket.chat/rest-typings/dist/v1/groups/BaseProps'; import { isTruthy } from '@rocket.chat/tools'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -31,16 +39,12 @@ import { executeGetRoomRoles } from '../../../lib/server/methods/getRoomRoles'; import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom'; import { executeUnarchiveRoom } from '../../../lib/server/methods/unarchiveRoom'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; import { addUserToFileObj } from '../helpers/addUserToFileObj'; import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage'; import { getPaginationItems } from '../helpers/getPaginationItems'; import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFromParams'; -import { ExtractRoutesFromAPI } from '../ApiClass'; -import { validateBadRequestErrorResponse } from '@rocket.chat/rest-typings'; -import { validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings'; -import { withUserIdProps } from '@rocket.chat/rest-typings/dist/v1/groups/BaseProps'; - async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise { if ( @@ -896,43 +900,36 @@ API.v1.addRoute( }, ); - - - - - - const groupsEndpoints = API.v1.post( - "groups.removeModerator", + 'groups.removeModerator', { - authRequired:true, + authRequired: true, body: withUserIdProps, - response:{ - 400:validateBadRequestErrorResponse, - 401:validateUnauthorizedErrorResponse, - 200:ajv.compile({ - type:'object', - properties:{ - success:{type:'boolean', enum:[true]}, - }, - required:['success'], - additionalProperties:false, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 200: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [true] }, + }, + required: ['success'], + additionalProperties: false, }), }, }, async function action() { - const findResult = await findPrivateGroupByIdOrName({ - params: this.bodyParams, - userId: this.userId, - }); + const findResult = await findPrivateGroupByIdOrName({ + params: this.bodyParams, + userId: this.userId, + }); - const user = await getUserFromParams(this.bodyParams); + const user = await getUserFromParams(this.bodyParams); - await removeRoomModerator(this.userId, findResult.rid, user._id); + await removeRoomModerator(this.userId, findResult.rid, user._id); - return API.v1.success(); + return API.v1.success(); }, - ); API.v1.addRoute( @@ -1326,10 +1323,9 @@ API.v1.addRoute( }, ); - export type GroupEndpoints = ExtractRoutesFromAPI; declare module '@rocket.chat/rest-typings' { - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface interface Endpoints extends GroupEndpoints {} -} \ No newline at end of file +} diff --git a/packages/rest-typings/src/v1/groups/index.ts b/packages/rest-typings/src/v1/groups/index.ts index aad463d285c9d..bb2a080b9b7f9 100644 --- a/packages/rest-typings/src/v1/groups/index.ts +++ b/packages/rest-typings/src/v1/groups/index.ts @@ -25,7 +25,6 @@ export * from './GroupsOnlineProps'; export * from './GroupsOpenProps'; export * from './GroupsRenameProps'; export * from './GroupsRemoveLeaderProps'; -export * from './GroupsRemoveModeratorProps'; export * from './GroupsRemoveOwnerProps'; export * from './GroupsSetAnnouncementProps'; export * from './GroupsSetCustomFieldsProps'; From a99ad292d96eabaada053bd342e90c2c3aa38cb7 Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Fri, 6 Mar 2026 17:14:03 +0000 Subject: [PATCH 5/8] feat: Standardize endpoint success response and update import path. --- apps/meteor/app/api/server/v1/groups.ts | 6 +++--- packages/rest-typings/src/v1/groups/index.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 0fd3c4cdf4a0e..1c52b89f2f959 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -8,8 +8,8 @@ import { ajv, validateBadRequestErrorResponse, validateUnauthorizedErrorResponse, + withUserIdProps } from '@rocket.chat/rest-typings'; -import { withUserIdProps } from '@rocket.chat/rest-typings/dist/v1/groups/BaseProps'; import { isTruthy } from '@rocket.chat/tools'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -908,7 +908,7 @@ const groupsEndpoints = API.v1.post( response: { 400: validateBadRequestErrorResponse, 401: validateUnauthorizedErrorResponse, - 200: ajv.compile({ + 200: ajv.compile<{ success: true }>({ type: 'object', properties: { success: { type: 'boolean', enum: [true] }, @@ -928,7 +928,7 @@ const groupsEndpoints = API.v1.post( await removeRoomModerator(this.userId, findResult.rid, user._id); - return API.v1.success(); + return API.v1.success({ success: true }); }, ); diff --git a/packages/rest-typings/src/v1/groups/index.ts b/packages/rest-typings/src/v1/groups/index.ts index bb2a080b9b7f9..7de0b421686d1 100644 --- a/packages/rest-typings/src/v1/groups/index.ts +++ b/packages/rest-typings/src/v1/groups/index.ts @@ -1,5 +1,6 @@ export type * from './groups'; +export * from './BaseProps'; export * from './GroupsArchiveProps'; export * from './GroupsCloseProps'; export * from './GroupsConvertToTeamProps'; From 044a50ec0f9341cbbdb8e10eaecfb05d854fc5c2 Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Sat, 7 Mar 2026 07:00:39 +0000 Subject: [PATCH 6/8] Refactor --- .changeset/groups.removeModerator.md | 1 + apps/meteor/app/api/server/v1/groups.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.changeset/groups.removeModerator.md b/.changeset/groups.removeModerator.md index a00137d2a8eb8..426009eebbc66 100644 --- a/.changeset/groups.removeModerator.md +++ b/.changeset/groups.removeModerator.md @@ -1,3 +1,4 @@ +--- "@rocket.chat/meteor": minor "@rocket.chat/rest-typings": minor --- diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 1c52b89f2f959..00cbc2c486da6 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -908,7 +908,7 @@ const groupsEndpoints = API.v1.post( response: { 400: validateBadRequestErrorResponse, 401: validateUnauthorizedErrorResponse, - 200: ajv.compile<{ success: true }>({ + 200: ajv.compile({ type: 'object', properties: { success: { type: 'boolean', enum: [true] }, @@ -928,7 +928,7 @@ const groupsEndpoints = API.v1.post( await removeRoomModerator(this.userId, findResult.rid, user._id); - return API.v1.success({ success: true }); + return API.v1.success(); }, ); @@ -1323,7 +1323,7 @@ API.v1.addRoute( }, ); -export type GroupEndpoints = ExtractRoutesFromAPI; +type GroupEndpoints = ExtractRoutesFromAPI; declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface From a03de4530d660e76388648ac8ab9128b7ebe21cd Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Sat, 7 Mar 2026 14:06:13 +0000 Subject: [PATCH 7/8] Refactor schema --- apps/meteor/app/api/server/v1/groups.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 00cbc2c486da6..d9ce9e6939528 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -8,7 +8,8 @@ import { ajv, validateBadRequestErrorResponse, validateUnauthorizedErrorResponse, - withUserIdProps + withGroupBaseProperties, + GroupsBaseProps } from '@rocket.chat/rest-typings'; import { isTruthy } from '@rocket.chat/tools'; import { check, Match } from 'meteor/check'; @@ -76,7 +77,7 @@ async function getRoomFromParams(params: { roomId?: string } | { roomName?: stri } })(); - if (!room || room.t !== 'p') { + if (room?.t !== 'p') { throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group'); } @@ -282,7 +283,7 @@ API.v1.addRoute( room = await Rooms.findOneByName(params.roomName || ''); } - if (!room || room.t !== 'p') { + if (room?.t !== 'p') { throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group'); } @@ -800,7 +801,7 @@ API.v1.addRoute( rid: findResult.rid, ...parseIds(mentionIds, 'mentions._id'), ...parseIds(starredIds, 'starred._id'), - ...(pinned && pinned.toLowerCase() === 'true' ? { pinned: true } : {}), + ...(pinned?.toLowerCase() === 'true' ? { pinned: true } : {}), _hidden: { $ne: true }, }; @@ -900,6 +901,18 @@ API.v1.addRoute( }, ); +type WithUserId = GroupsBaseProps & { userId: string }; +const withUserIdSchema = withGroupBaseProperties( + { + userId: { + type: 'string', + }, + }, + ['userId'], +); + +const withUserIdProps = ajv.compile(withUserIdSchema); + const groupsEndpoints = API.v1.post( 'groups.removeModerator', { From 84f37a4b46317759cdf2188b315c19919c1fc7f2 Mon Sep 17 00:00:00 2001 From: Harshal Sewatkar Date: Sat, 7 Mar 2026 14:16:27 +0000 Subject: [PATCH 8/8] remove import from rest-typing index.ts --- packages/rest-typings/src/v1/groups/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rest-typings/src/v1/groups/index.ts b/packages/rest-typings/src/v1/groups/index.ts index 7de0b421686d1..bb2a080b9b7f9 100644 --- a/packages/rest-typings/src/v1/groups/index.ts +++ b/packages/rest-typings/src/v1/groups/index.ts @@ -1,6 +1,5 @@ export type * from './groups'; -export * from './BaseProps'; export * from './GroupsArchiveProps'; export * from './GroupsCloseProps'; export * from './GroupsConvertToTeamProps';