diff --git a/.changeset/migrate-rooms-mute-unmute-endpoints.md b/.changeset/migrate-rooms-mute-unmute-endpoints.md new file mode 100644 index 0000000000000..37437b3b92801 --- /dev/null +++ b/.changeset/migrate-rooms-mute-unmute-endpoints.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/rest-typings": minor +--- + +Migrated `rooms.muteUser` and `rooms.unmuteUser` endpoints to the new OpenAPI pattern with AJV schema validation diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index f4ed50a0a0c9e..12f305b8ff0a7 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -8,7 +8,6 @@ import { ajvQuery, isGETRoomsNameExists, isRoomsImagesProps, - isRoomsMuteUnmuteUserProps, isRoomsExportProps, isRoomsIsMemberProps, isRoomsCleanHistoryProps, @@ -123,61 +122,6 @@ API.v1.addRoute( }, ); -const roomDeleteEndpoint = API.v1.post( - 'rooms.delete', - { - authRequired: true, - body: ajv.compile<{ roomId: string }>({ - type: 'object', - properties: { - roomId: { - type: 'string', - description: 'The ID of the room to delete.', - }, - }, - required: ['roomId'], - additionalProperties: false, - }), - response: { - 200: ajv.compile({ - type: 'object', - properties: { - success: { - type: 'boolean', - enum: [true], - description: 'Indicates if the request was successful.', - }, - }, - required: ['success'], - additionalProperties: false, - }), - 400: validateBadRequestErrorResponse, - 401: validateUnauthorizedErrorResponse, - }, - }, - async function action() { - const { roomId } = this.bodyParams; - - const room = await Rooms.findOneById(roomId); - - if (!room) { - throw new MeteorError('error-invalid-room', 'Invalid room', { - method: 'eraseRoom', - }); - } - - if (room.teamMain) { - throw new Meteor.Error('error-cannot-delete-team-channel', 'Cannot delete a team channel', { - method: 'eraseRoom', - }); - } - - await eraseRoom(room, this.user); - - return API.v1.success(); - }, -); - API.v1.addRoute( 'rooms.get', { authRequired: true }, @@ -318,56 +262,6 @@ API.v1.addRoute( }, ); -const saveNotificationBodySchema = ajv.compile<{ - roomId: string; - notifications: Record; -}>({ - type: 'object', - properties: { - roomId: { type: 'string', minLength: 1 }, - notifications: { - type: 'object', - minProperties: 1, - additionalProperties: { type: 'string' }, - }, - }, - required: ['roomId', 'notifications'], - additionalProperties: false, -}); - -const saveNotificationResponseSchema = ajv.compile({ - type: 'object', - properties: { - success: { type: 'boolean', enum: [true] }, - }, - required: ['success'], - additionalProperties: false, -}); - -const roomsSaveNotificationEndpoint = API.v1.post( - 'rooms.saveNotification', - { - authRequired: true, - body: saveNotificationBodySchema, - response: { - 200: saveNotificationResponseSchema, - 400: validateBadRequestErrorResponse, - 401: validateUnauthorizedErrorResponse, - }, - }, - async function action() { - const { roomId, notifications } = this.bodyParams; - - await Promise.all( - Object.entries(notifications as Notifications).map(async ([notificationKey, notificationValue]) => - saveNotificationSettingsMethod(this.userId, roomId, notificationKey as NotificationFieldType, notificationValue), - ), - ); - - return API.v1.success({ success: true }); - }, -); - API.v1.addRoute( 'rooms.cleanHistory', { authRequired: true, validateParams: isRoomsCleanHistoryProps }, @@ -886,42 +780,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'rooms.muteUser', - { authRequired: true, validateParams: isRoomsMuteUnmuteUserProps }, - { - async post() { - const user = await getUserFromParams(this.bodyParams); - - if (!user.username) { - return API.v1.failure('Invalid user'); - } - - await muteUserInRoom(this.userId, { rid: this.bodyParams.roomId, username: user.username }); - - return API.v1.success(); - }, - }, -); - -API.v1.addRoute( - 'rooms.unmuteUser', - { authRequired: true, validateParams: isRoomsMuteUnmuteUserProps }, - { - async post() { - const user = await getUserFromParams(this.bodyParams); - - if (!user.username) { - return API.v1.failure('Invalid user'); - } - - await unmuteUserInRoom(this.userId, { rid: this.bodyParams.roomId, username: user.username }); - - return API.v1.success(); - }, - }, -); - API.v1.addRoute( 'rooms.open', { authRequired: true, validateParams: isRoomsOpenProps }, @@ -1038,6 +896,74 @@ const isRoomsLeavePropsSchema = { const isRoomsFavoriteProps = ajv.compile(RoomsFavoriteSchema); const isRoomsLeaveProps = ajv.compile(isRoomsLeavePropsSchema); +type RoomsMuteUnmuteUser = { userId: string; roomId: string } | { username: string; roomId: string }; + +const RoomsMuteUnmuteUserSchema = { + type: 'object', + oneOf: [ + { + properties: { + userId: { + type: 'string', + minLength: 1, + }, + roomId: { + type: 'string', + minLength: 1, + }, + }, + required: ['userId', 'roomId'], + additionalProperties: false, + }, + { + properties: { + username: { + type: 'string', + minLength: 1, + }, + roomId: { + type: 'string', + minLength: 1, + }, + }, + required: ['username', 'roomId'], + additionalProperties: false, + }, + ], +}; + +const isRoomsMuteUnmuteUserProps = ajv.compile(RoomsMuteUnmuteUserSchema); + +type SaveNotificationBody = { + roomId: string; + notifications: Record; +}; + +const saveNotificationBodySchema = { + type: 'object', + properties: { + roomId: { type: 'string', minLength: 1 }, + notifications: { + type: 'object', + minProperties: 1, + additionalProperties: { type: 'string' }, + }, + }, + required: ['roomId', 'notifications'], + additionalProperties: false, +}; + +const isSaveNotificationBodyProps = ajv.compile(saveNotificationBodySchema); + +const saveNotificationResponseSchema = ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [true] }, + }, + required: ['success'], + additionalProperties: false, +}); + export const roomEndpoints = API.v1 .get( 'rooms.roles', @@ -1184,6 +1110,29 @@ export const roomEndpoints = API.v1 } }, ) + .post( + 'rooms.saveNotification', + { + authRequired: true, + body: isSaveNotificationBodyProps, + response: { + 200: saveNotificationResponseSchema, + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + }, + }, + async function action() { + const { roomId, notifications } = this.bodyParams; + + await Promise.all( + Object.entries(notifications as Notifications).map(async ([notificationKey, notificationValue]) => + saveNotificationSettingsMethod(this.userId, roomId, notificationKey as NotificationFieldType, notificationValue), + ), + ); + + return API.v1.success({ success: true }); + }, + ) .post( 'rooms.favorite', { @@ -1216,6 +1165,60 @@ export const roomEndpoints = API.v1 return API.v1.success(); }, ) + .post( + 'rooms.delete', + { + authRequired: true, + body: ajv.compile<{ roomId: string }>({ + type: 'object', + properties: { + roomId: { + type: 'string', + description: 'The ID of the room to delete.', + }, + }, + required: ['roomId'], + additionalProperties: false, + }), + response: { + 200: ajv.compile({ + type: 'object', + properties: { + success: { + type: 'boolean', + enum: [true], + description: 'Indicates if the request was successful.', + }, + }, + required: ['success'], + additionalProperties: false, + }), + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + }, + }, + async function action() { + const { roomId } = this.bodyParams; + + const room = await Rooms.findOneById(roomId); + + if (!room) { + throw new MeteorError('error-invalid-room', 'Invalid room', { + method: 'eraseRoom', + }); + } + + if (room.teamMain) { + throw new Meteor.Error('error-cannot-delete-team-channel', 'Cannot delete a team channel', { + method: 'eraseRoom', + }); + } + + await eraseRoom(room, this.user); + + return API.v1.success(); + }, + ) .post( 'rooms.leave', { @@ -1245,13 +1248,71 @@ export const roomEndpoints = API.v1 await leaveRoomMethod(user, room._id); + return API.v1.success(); + }, + ) + .post( + 'rooms.muteUser', + { + authRequired: true, + body: isRoomsMuteUnmuteUserProps, + response: { + 200: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [true] }, + }, + required: ['success'], + additionalProperties: false, + }), + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + }, + }, + async function action() { + const user = await getUserFromParams(this.bodyParams); + + if (!user.username) { + return API.v1.failure('Invalid user'); + } + + await muteUserInRoom(this.userId, { rid: this.bodyParams.roomId, username: user.username }); + + return API.v1.success(); + }, + ) + .post( + 'rooms.unmuteUser', + { + authRequired: true, + body: isRoomsMuteUnmuteUserProps, + response: { + 200: ajv.compile({ + type: 'object', + properties: { + success: { type: 'boolean', enum: [true] }, + }, + required: ['success'], + additionalProperties: false, + }), + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + }, + }, + async function action() { + const user = await getUserFromParams(this.bodyParams); + + if (!user.username) { + return API.v1.failure('Invalid user'); + } + + await unmuteUserInRoom(this.userId, { rid: this.bodyParams.roomId, username: user.username }); + return API.v1.success(); }, ); -type RoomEndpoints = ExtractRoutesFromAPI & - 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 112a8eff276a5..ea090a2f7317d 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -485,43 +485,6 @@ export type Notifications = { type RoomsGetDiscussionsProps = PaginatedRequest; -type RoomsMuteUnmuteUser = { userId: string; roomId: string } | { username: string; roomId: string }; - -const RoomsMuteUnmuteUserSchema = { - type: 'object', - oneOf: [ - { - properties: { - userId: { - type: 'string', - minLength: 1, - }, - roomId: { - type: 'string', - minLength: 1, - }, - }, - required: ['userId', 'roomId'], - additionalProperties: false, - }, - { - properties: { - username: { - type: 'string', - minLength: 1, - }, - roomId: { - type: 'string', - minLength: 1, - }, - }, - required: ['username', 'roomId'], - additionalProperties: false, - }, - ], -}; - -export const isRoomsMuteUnmuteUserProps = ajv.compile(RoomsMuteUnmuteUserSchema); export type RoomsImagesProps = { roomId: string; startingFromId?: string; @@ -827,14 +790,6 @@ export type RoomsEndpoints = { GET: (params: RoomsIsMemberProps) => { isMember: boolean }; }; - '/v1/rooms.muteUser': { - POST: (params: RoomsMuteUnmuteUser) => void; - }; - - '/v1/rooms.unmuteUser': { - POST: (params: RoomsMuteUnmuteUser) => void; - }; - '/v1/rooms.images': { GET: (params: RoomsImagesProps) => PaginatedResult<{ files: IUpload[];