diff --git a/.changeset/three-turkeys-dress.md b/.changeset/three-turkeys-dress.md new file mode 100644 index 0000000000000..c7300ed8d7a73 --- /dev/null +++ b/.changeset/three-turkeys-dress.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Adds deprecation warning on `livechat:removeRoom` with new endpoint replacing it; `livechat/rooms.delete` diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index 26c31abe83634..42a1b5666fe73 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -19,6 +19,11 @@ import { isPOSTLivechatRoomCloseByUserParams, isPOSTLivechatRoomsCloseAll, isPOSTLivechatRoomsCloseAllSuccessResponse, + POSTLivechatRemoveRoomSuccess, + isPOSTLivechatRemoveRoomParams, + validateBadRequestErrorResponse, + validateUnauthorizedErrorResponse, + validateForbiddenErrorResponse, } from '@rocket.chat/rest-typings'; import { check } from 'meteor/check'; @@ -438,32 +443,61 @@ API.v1.addRoute( }, ); -const livechatRoomsEndpoints = API.v1.post( - 'livechat/rooms.removeAllClosedRooms', - { - response: { - 200: isPOSTLivechatRoomsCloseAllSuccessResponse, +const livechatRoomsEndpoints = API.v1 + .post( + 'livechat/rooms.delete', + { + response: { + 200: POSTLivechatRemoveRoomSuccess, + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + }, + authRequired: true, + permissionsRequired: ['remove-closed-livechat-room'], + body: isPOSTLivechatRemoveRoomParams, }, - authRequired: true, - permissionsRequired: ['remove-closed-livechat-rooms'], - body: isPOSTLivechatRoomsCloseAll, - }, - async function action() { - livechatLogger.info(`User ${this.userId} is removing all closed rooms`); + async function action() { + const { roomId } = this.bodyParams; + + try { + await removeOmnichannelRoom(roomId); + return API.v1.success(); + } catch (error: unknown) { + if (error instanceof Meteor.Error) { + return API.v1.failure(error.reason); + } + + return API.v1.failure('error-removing-room'); + } + }, + ) + .post( + 'livechat/rooms.removeAllClosedRooms', + { + response: { + 200: isPOSTLivechatRoomsCloseAllSuccessResponse, + }, + authRequired: true, + permissionsRequired: ['remove-closed-livechat-rooms'], + body: isPOSTLivechatRoomsCloseAll, + }, + async function action() { + livechatLogger.info(`User ${this.userId} is removing all closed rooms`); - const params = this.bodyParams; + const params = this.bodyParams; - const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}, { userId: this.userId }); - const promises: Promise[] = []; - await LivechatRooms.findClosedRooms(params?.departmentIds, {}, extraQuery).forEach(({ _id }: IOmnichannelRoom) => { - promises.push(removeOmnichannelRoom(_id)); - }); - await Promise.all(promises); + const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}, { userId: this.userId }); + const promises: Promise[] = []; + await LivechatRooms.findClosedRooms(params?.departmentIds, {}, extraQuery).forEach(({ _id }: IOmnichannelRoom) => { + promises.push(removeOmnichannelRoom(_id)); + }); + await Promise.all(promises); - livechatLogger.info(`User ${this.userId} removed ${promises.length} closed rooms`); - return API.v1.success({ removedRooms: promises.length }); - }, -); + livechatLogger.info(`User ${this.userId} removed ${promises.length} closed rooms`); + return API.v1.success({ removedRooms: promises.length }); + }, + ); type LivechatRoomsEndpoints = ExtractRoutesFromAPI; diff --git a/apps/meteor/app/livechat/server/lib/rooms.ts b/apps/meteor/app/livechat/server/lib/rooms.ts index 00ede94ce8a93..486adee979cd6 100644 --- a/apps/meteor/app/livechat/server/lib/rooms.ts +++ b/apps/meteor/app/livechat/server/lib/rooms.ts @@ -9,6 +9,7 @@ import type { IOmnichannelRoom, TransferData, } from '@rocket.chat/core-typings'; +import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatRooms, LivechatContacts, @@ -267,6 +268,14 @@ export async function removeOmnichannelRoom(rid: string) { throw new Meteor.Error('error-invalid-room', 'Invalid room'); } + if (!isOmnichannelRoom(room)) { + throw new Meteor.Error('error-this-is-not-a-livechat-room'); + } + + if (room.open) { + throw new Meteor.Error('error-room-is-not-closed'); + } + const inquiry = await LivechatInquiry.findOneByRoomId(rid); const result = await Promise.allSettled([ diff --git a/apps/meteor/app/livechat/server/methods/removeRoom.ts b/apps/meteor/app/livechat/server/methods/removeRoom.ts index 7d659a15985e1..bfe38121782ef 100644 --- a/apps/meteor/app/livechat/server/methods/removeRoom.ts +++ b/apps/meteor/app/livechat/server/methods/removeRoom.ts @@ -4,6 +4,7 @@ import { LivechatRooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { removeOmnichannelRoom } from '../lib/rooms'; declare module '@rocket.chat/ddp-client' { @@ -15,6 +16,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async 'livechat:removeRoom'(rid) { + methodDeprecationLogger.method('livechat:removeRoom', '8.0.0', '/v1/livechat/rooms.delete'); const user = Meteor.userId(); if (!user || !(await hasPermissionAsync(user, 'remove-closed-livechat-room'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeRoom' }); diff --git a/apps/meteor/client/views/omnichannel/currentChats/hooks/useRemoveCurrentChatMutation.ts b/apps/meteor/client/views/omnichannel/currentChats/hooks/useRemoveCurrentChatMutation.ts index be59e98b26779..d609505017a07 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/hooks/useRemoveCurrentChatMutation.ts +++ b/apps/meteor/client/views/omnichannel/currentChats/hooks/useRemoveCurrentChatMutation.ts @@ -1,16 +1,16 @@ import type { IRoom } from '@rocket.chat/core-typings'; -import { useMethod } from '@rocket.chat/ui-contexts'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useQueryClient, useMutation } from '@tanstack/react-query'; export const useRemoveCurrentChatMutation = ( - options?: Omit, 'mutationFn'>, -): UseMutationResult => { - const removeRoom = useMethod('livechat:removeRoom'); + options?: Omit, 'mutationFn'>, +): UseMutationResult => { + const removeRoom = useEndpoint('POST', '/v1/livechat/rooms.delete'); const queryClient = useQueryClient(); return useMutation({ - mutationFn: (rid) => removeRoom(rid), + mutationFn: (rid) => removeRoom({ roomId: rid }), ...options, onSuccess: (...args) => { diff --git a/apps/meteor/tests/e2e/utils/omnichannel/rooms.ts b/apps/meteor/tests/e2e/utils/omnichannel/rooms.ts index e34353672fe14..4e5281e41dedd 100644 --- a/apps/meteor/tests/e2e/utils/omnichannel/rooms.ts +++ b/apps/meteor/tests/e2e/utils/omnichannel/rooms.ts @@ -54,14 +54,7 @@ export const createRoom = async (api: BaseTest['api'], { visitorToken, agentId } data: room, async delete() { await closeRoom(api, { roomId: room._id, visitorToken }); - return api.post('/method.call/livechat:removeRoom', { - message: JSON.stringify({ - msg: 'method', - id: '16', - method: 'livechat:removeRoom', - params: [room._id], - }), - }); + return api.post('/livechat/rooms.delete', { roomId: room._id }); }, }; }; diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 164c5624000eb..21177c177faef 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -4109,6 +4109,53 @@ export const isPOSTLivechatRoomsCloseAllSuccessResponse = ajv.compile<{ removedR POSTLivechatRoomsCloseAllSuccessResponseSchema, ); +type POSTLivechatRemoveRoomParams = { + roomId: string; +}; + +const POSTLivechatRemoveRoomParamsSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + }, + required: ['roomId'], + additionalProperties: false, +}; + +export const isPOSTLivechatRemoveRoomParams = ajv.compile(POSTLivechatRemoveRoomParamsSchema); + +const POSTLivechatRemoveRoomSuccessSchema = { + type: 'object', + properties: { + success: { + type: 'boolean', + enum: [true], + }, + }, + additionalProperties: false, +}; + +export const POSTLivechatRemoveRoomSuccess = ajv.compile(POSTLivechatRemoveRoomSuccessSchema); + +type POSTLivechatRemoveCustomFields = { + customFieldId: string; +}; + +const POSTLivechatRemoveCustomFieldsSchema = { + type: 'object', + properties: { + customFieldId: { + type: 'string', + }, + }, + required: ['customFieldId'], + additionalProperties: false, +}; + +export const isPOSTLivechatRemoveCustomFields = ajv.compile(POSTLivechatRemoveCustomFieldsSchema); + const POSTLivechatSaveCustomFieldsSchema = { type: 'object', properties: { @@ -4228,23 +4275,6 @@ export const POSTLivechatSaveCustomFieldSuccess = ajv.compile<{ customField: ILi POSTLivechatSaveCustomFieldSuccessSchema, ); -type POSTLivechatRemoveCustomFields = { - customFieldId: string; -}; - -const POSTLivechatRemoveCustomFieldsSchema = { - type: 'object', - properties: { - customFieldId: { - type: 'string', - }, - }, - required: ['customFieldId'], - additionalProperties: false, -}; - -export const isPOSTLivechatRemoveCustomFields = ajv.compile(POSTLivechatRemoveCustomFieldsSchema); - const POSTLivechatRemoveCustomFieldSuccessSchema = { type: 'object', properties: {