From 7f22793e80c47df66547efe78ce030ac28663443 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Sun, 15 Dec 2024 12:53:06 -0300 Subject: [PATCH] chore: Remove openRoom client meteor method (#33833) --- apps/meteor/app/api/server/v1/rooms.ts | 16 +++++++ apps/meteor/client/main.ts | 2 +- apps/meteor/client/methods/index.ts | 1 - apps/meteor/client/methods/openRoom.ts | 26 ---------- .../client/views/room/hooks/useOpenRoom.ts | 7 +-- .../views/room/hooks/useOpenRoomMutation.tsx | 32 +++++++++++++ apps/meteor/server/lib/openRoom.ts | 13 +++++ apps/meteor/server/methods/openRoom.ts | 11 +---- apps/meteor/tests/end-to-end/api/rooms.ts | 48 +++++++++++++++++++ packages/rest-typings/src/v1/rooms.ts | 22 +++++++++ 10 files changed, 138 insertions(+), 40 deletions(-) delete mode 100644 apps/meteor/client/methods/index.ts delete mode 100644 apps/meteor/client/methods/openRoom.ts create mode 100644 apps/meteor/client/views/room/hooks/useOpenRoomMutation.tsx create mode 100644 apps/meteor/server/lib/openRoom.ts diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index e8a152db914a..00fa0007df15 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -9,6 +9,7 @@ import { isRoomsExportProps, isRoomsIsMemberProps, isRoomsCleanHistoryProps, + isRoomsOpenProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -16,6 +17,7 @@ import { isTruthy } from '../../../../lib/isTruthy'; import { omit } from '../../../../lib/utils/omit'; import * as dataExport from '../../../../server/lib/dataExport'; import { eraseRoom } from '../../../../server/lib/eraseRoom'; +import { openRoom } from '../../../../server/lib/openRoom'; import { muteUserInRoom } from '../../../../server/methods/muteUserInRoom'; import { unmuteUserInRoom } from '../../../../server/methods/unmuteUserInRoom'; import { canAccessRoomAsync, canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; @@ -893,3 +895,17 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'rooms.open', + { authRequired: true, validateParams: isRoomsOpenProps }, + { + async post() { + const { roomId } = this.bodyParams; + + await openRoom(this.userId, roomId); + + return API.v1.success(); + }, + }, +); diff --git a/apps/meteor/client/main.ts b/apps/meteor/client/main.ts index d66b5bcec2de..62da42ae4198 100644 --- a/apps/meteor/client/main.ts +++ b/apps/meteor/client/main.ts @@ -13,6 +13,6 @@ import('./polyfills') .then(() => import('./meteorOverrides')) .then(() => import('./ecdh')) .then(() => import('./importPackages')) - .then(() => Promise.all([import('./methods'), import('./startup')])) + .then(() => import('./startup')) .then(() => import('./omnichannel')) .then(() => Promise.all([import('./views/admin'), import('./views/marketplace'), import('./views/account')])); diff --git a/apps/meteor/client/methods/index.ts b/apps/meteor/client/methods/index.ts deleted file mode 100644 index 6e054fd54892..000000000000 --- a/apps/meteor/client/methods/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './openRoom'; diff --git a/apps/meteor/client/methods/openRoom.ts b/apps/meteor/client/methods/openRoom.ts deleted file mode 100644 index afe12060ad50..000000000000 --- a/apps/meteor/client/methods/openRoom.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Meteor } from 'meteor/meteor'; - -import { Subscriptions } from '../../app/models/client'; - -Meteor.methods({ - async openRoom(rid) { - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'openRoom', - }); - } - - return Subscriptions.update( - { - rid, - 'u._id': Meteor.userId(), - }, - { - $set: { - open: true, - }, - }, - ); - }, -}); diff --git a/apps/meteor/client/views/room/hooks/useOpenRoom.ts b/apps/meteor/client/views/room/hooks/useOpenRoom.ts index 9937af4ecef7..6c7326f94d76 100644 --- a/apps/meteor/client/views/room/hooks/useOpenRoom.ts +++ b/apps/meteor/client/views/room/hooks/useOpenRoom.ts @@ -3,6 +3,7 @@ import { useMethod, useRoute, useSetting, useUser } from '@rocket.chat/ui-contex import { useQuery } from '@tanstack/react-query'; import { useRef } from 'react'; +import { useOpenRoomMutation } from './useOpenRoomMutation'; import { roomFields } from '../../../../lib/publishFields'; import { omit } from '../../../../lib/utils/omit'; import { NotAuthorizedError } from '../../../lib/errors/NotAuthorizedError'; @@ -15,8 +16,8 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead', true); const getRoomByTypeAndName = useMethod('getRoomByTypeAndName'); const createDirectMessage = useMethod('createDirectMessage'); - const openRoom = useMethod('openRoom'); const directRoute = useRoute('direct'); + const openRoom = useOpenRoomMutation(); const unsubscribeFromRoomOpenedEvent = useRef<() => void>(() => undefined); @@ -96,8 +97,8 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st // update user's room subscription const sub = Subscriptions.findOne({ rid: room._id }); - if (sub && !sub.open) { - await openRoom(room._id); + if (!!user?._id && sub && !sub.open) { + await openRoom.mutateAsync({ roomId: room._id, userId: user._id }); } return { rid: room._id }; }, diff --git a/apps/meteor/client/views/room/hooks/useOpenRoomMutation.tsx b/apps/meteor/client/views/room/hooks/useOpenRoomMutation.tsx new file mode 100644 index 000000000000..65fbe785bffc --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useOpenRoomMutation.tsx @@ -0,0 +1,32 @@ +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useMutation } from '@tanstack/react-query'; + +import { updateSubscription } from '../../../lib/mutationEffects/updateSubscription'; + +type OpenRoomParams = { + roomId: string; + userId: string; +}; + +export const useOpenRoomMutation = () => { + const openRoom = useEndpoint('POST', '/v1/rooms.open'); + + return useMutation({ + mutationFn: async ({ roomId, userId }: OpenRoomParams) => { + await openRoom({ roomId }); + + return { userId, roomId }; + }, + onMutate: async ({ roomId, userId }) => { + return updateSubscription(roomId, userId, { open: true }); + }, + onError: async (_, { roomId, userId }, rollbackDocument) => { + if (!rollbackDocument) { + return; + } + + const { open } = rollbackDocument; + await updateSubscription(roomId, userId, { open }); + }, + }); +}; diff --git a/apps/meteor/server/lib/openRoom.ts b/apps/meteor/server/lib/openRoom.ts new file mode 100644 index 000000000000..cf5b615bde09 --- /dev/null +++ b/apps/meteor/server/lib/openRoom.ts @@ -0,0 +1,13 @@ +import { Subscriptions } from '@rocket.chat/models'; + +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; + +export async function openRoom(userId: string, roomId: string) { + const openByRoomResponse = await Subscriptions.openByRoomIdAndUserId(roomId, userId); + + if (openByRoomResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(roomId, userId); + } + + return openByRoomResponse.modifiedCount; +} diff --git a/apps/meteor/server/methods/openRoom.ts b/apps/meteor/server/methods/openRoom.ts index 440de52b87fb..354da36f60eb 100644 --- a/apps/meteor/server/methods/openRoom.ts +++ b/apps/meteor/server/methods/openRoom.ts @@ -1,10 +1,9 @@ import type { IRoom } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; -import { Subscriptions } from '@rocket.chat/models'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../app/lib/server/lib/notifyListener'; +import { openRoom } from '../lib/openRoom'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -25,12 +24,6 @@ Meteor.methods({ }); } - const openByRoomResponse = await Subscriptions.openByRoomIdAndUserId(rid, uid); - - if (openByRoomResponse.modifiedCount) { - void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); - } - - return openByRoomResponse.modifiedCount; + return openRoom(uid, rid); }, }); diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index ca033ef88bf8..110eb22a83de 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -3317,6 +3317,7 @@ describe('[Rooms]', () => { }); }); }); + describe('/rooms.isMember', () => { let testChannel: IRoom; let testGroup: IRoom; @@ -3599,4 +3600,51 @@ describe('[Rooms]', () => { }); }); }); + + describe('/rooms.open', () => { + let room: IRoom; + + before(async () => { + room = (await createRoom({ type: 'c', name: `rooms.open.test.${Date.now()}` })).body.channel; + }); + + after(async () => { + await deleteRoom({ type: 'c', roomId: room._id }); + }); + + it('should open the room', (done) => { + void request + .post(api('rooms.open')) + .set(credentials) + .send({ roomId: room._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + void request + .get(api('subscriptions.getOne')) + .set(credentials) + .query({ roomId: room._id }) + .send() + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.subscription).to.have.property('open', true); + }) + .end(done); + }); + + it('should fail if roomId is not provided', async () => { + await request + .post(api('rooms.open')) + .set(credentials) + .send() + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); }); diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 0f420b486a3b..e9b28a51140c 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -598,6 +598,24 @@ const roomsCleanHistorySchema = { export const isRoomsCleanHistoryProps = ajv.compile(roomsCleanHistorySchema); +type RoomsOpenProps = { + roomId: string; +}; + +const roomsOpenSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + minLength: 1, + }, + }, + required: ['roomId'], + additionalProperties: false, +}; + +export const isRoomsOpenProps = ajv.compile(roomsOpenSchema); + export type RoomsEndpoints = { '/v1/rooms.autocomplete.channelAndPrivate': { GET: (params: RoomsAutoCompleteChannelAndPrivateProps) => { @@ -764,4 +782,8 @@ export type RoomsEndpoints = { files: IUpload[]; }>; }; + + '/v1/rooms.open': { + POST: (params: RoomsOpenProps) => void; + }; };