From 12a909471601b59ea468d7141c316e3001d7d021 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 24 Jan 2024 18:30:50 +0530 Subject: [PATCH 01/39] added findByUsernameAndRoom to User model --- apps/meteor/server/models/raw/Users.js | 9 +++++++++ packages/model-typings/src/models/IUsersModel.ts | 1 + 2 files changed, 10 insertions(+) diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 0d56fc76bccf..622c530099e6 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -244,6 +244,15 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } + findOneByUsernameAndRoom(username, rid, options) { + const query = { + __rooms: rid, + username, + }; + + return this.findOne(query, options); + } + findOneByIdAndLoginHashedToken(_id, token, options = {}) { const query = { _id, diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index f9a2b1c45a2a..32599ed4ed6e 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -27,6 +27,7 @@ export interface IUsersModel extends IBaseModel { options: any, ): FindPaginated>; findOneByUsernameAndRoomIgnoringCase(username: string, rid: IRoom['_id'], options: any): FindCursor; + findOneByUsernameAndRoom(username: string, rid: IRoom['_id'], options: any): FindCursor; findOneByIdAndLoginHashedToken(_id: string, token: any, options?: any): FindCursor; findByActiveUsersExcept( searchTerm: any, From 65f0e0e06df2a7a803a24a1deca8a632e30b8cf3 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 24 Jan 2024 18:31:42 +0530 Subject: [PATCH 02/39] added groups/channels/im membercheck endpoints --- apps/meteor/app/api/server/v1/channels.ts | 32 ++++++++++++++++ apps/meteor/app/api/server/v1/groups.ts | 32 ++++++++++++++++ apps/meteor/app/api/server/v1/im.ts | 38 +++++++++++++++++++ .../rest-typings/src/v1/channels/channels.ts | 3 ++ packages/rest-typings/src/v1/dm/dm.ts | 1 + packages/rest-typings/src/v1/dm/im.ts | 5 +++ packages/rest-typings/src/v1/groups/groups.ts | 3 ++ 7 files changed, 114 insertions(+) diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 1c84926edb63..7ebe1adc72b5 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -1094,6 +1094,38 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + 'channels.memberExists', + { authRequired: true }, + { + async get() { + check( + this.queryParams, + Match.ObjectIncluding({ + username: String, + roomId: String, + }), + ); + + const { username, roomId } = this.queryParams; + + const findResult = await findChannelByIdOrName({ + params: { roomId }, + checkedArchived: false, + }); + + if (findResult.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', findResult._id))) { + return API.v1.unauthorized(); + } + + const options = { projection: { username: 1 } }; + const user = await Users.findOneByUsernameAndRoom(username, findResult._id, options); + + return API.v1.success({ exists: !!user }); + }, + }, +); + API.v1.addRoute( 'channels.online', { authRequired: true }, diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 34deb57304fc..e29a102367d2 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -747,6 +747,38 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + 'groups.memberExists', + { authRequired: true }, + { + async get() { + check( + this.queryParams, + Match.ObjectIncluding({ + username: String, + roomId: String, + }), + ); + + const { username, roomId } = this.queryParams; + + const findResult = await findPrivateGroupByIdOrName({ + params: { roomId }, + userId: this.userId, + }); + + if (findResult.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', findResult.rid))) { + return API.v1.unauthorized(); + } + + const options = { projection: { username: 1 } }; + const user = await Users.findOneByUsernameAndRoom(username, findResult.rid, options); + + return API.v1.success({ exists: !!user }); + }, + }, +); + API.v1.addRoute( 'groups.messages', { authRequired: true }, diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index a640318a9cd0..30027b1b515d 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -353,6 +353,44 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + ['dm.memberExists', 'im.memberExists'], + { + authRequired: true, + }, + { + async get() { + check( + this.queryParams, + Match.ObjectIncluding({ + username: String, + roomId: String, + }), + ); + + const { username, roomId } = this.queryParams; + + const { room } = await findDirectMessageRoom({ roomId }, this.userId); + + const canAccess = await canAccessRoomIdAsync(room._id, this.userId); + if (!canAccess) { + return API.v1.unauthorized(); + } + + const query = { + _id: { $in: room.uids }, + username, + }; + + const options = { projection: { _id: 1 } }; + + const user = await Users.findOne(query, options); + + return API.v1.success({ exists: !!user }); + }, + }, +); + API.v1.addRoute( ['dm.messages', 'im.messages'], { diff --git a/packages/rest-typings/src/v1/channels/channels.ts b/packages/rest-typings/src/v1/channels/channels.ts index 328588ffb58b..02379315f68d 100644 --- a/packages/rest-typings/src/v1/channels/channels.ts +++ b/packages/rest-typings/src/v1/channels/channels.ts @@ -53,6 +53,9 @@ export type ChannelsEndpoints = { members: IUser[]; }>; }; + '/v1/channels.memberExists': { + GET: (params: { roomId: string } | { roomName: string }) => { exists: boolean }; + }; '/v1/channels.history': { GET: (params: ChannelsHistoryProps) => PaginatedResult<{ messages: IMessage[]; diff --git a/packages/rest-typings/src/v1/dm/dm.ts b/packages/rest-typings/src/v1/dm/dm.ts index 80955beeef04..2d04f31207c2 100644 --- a/packages/rest-typings/src/v1/dm/dm.ts +++ b/packages/rest-typings/src/v1/dm/dm.ts @@ -8,6 +8,7 @@ export type DmEndpoints = { '/v1/dm.files': ImEndpoints['/v1/im.files']; '/v1/dm.history': ImEndpoints['/v1/im.history']; '/v1/dm.members': ImEndpoints['/v1/im.members']; + '/v1/dm.memberExists': ImEndpoints['/v1/im.memberExists']; '/v1/dm.messages': ImEndpoints['/v1/im.messages']; '/v1/dm.messages.others': ImEndpoints['/v1/im.messages.others']; '/v1/dm.list': ImEndpoints['/v1/im.list']; diff --git a/packages/rest-typings/src/v1/dm/im.ts b/packages/rest-typings/src/v1/dm/im.ts index 60d08abc56b2..6b3d96e95025 100644 --- a/packages/rest-typings/src/v1/dm/im.ts +++ b/packages/rest-typings/src/v1/dm/im.ts @@ -56,6 +56,11 @@ export type ImEndpoints = { members: Pick[]; }>; }; + + '/v1/im.memberExists': { + GET: (params: { username: string; roomId: string }) => { exists: boolean }; + }; + '/v1/im.messages': { GET: (params: DmMessagesProps) => PaginatedResult<{ messages: IMessage[]; diff --git a/packages/rest-typings/src/v1/groups/groups.ts b/packages/rest-typings/src/v1/groups/groups.ts index 529e086a81af..10fbb138e658 100644 --- a/packages/rest-typings/src/v1/groups/groups.ts +++ b/packages/rest-typings/src/v1/groups/groups.ts @@ -53,6 +53,9 @@ export type GroupsEndpoints = { total: number; }; }; + '/v1/groups.memberExists': { + GET: (params: { roomId: string; roomName: string }) => { exists: boolean }; + }; '/v1/groups.history': { GET: (params: GroupsHistoryProps) => PaginatedResult<{ messages: IMessage[]; From 432d554d1a4faa92ad6836d24027e76caf50fe99 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 24 Jan 2024 18:33:30 +0530 Subject: [PATCH 03/39] show only relevant userInfoActions for non-members --- .../client/views/hooks/useMemberExists.ts | 23 +++++ .../actions/useAddUserAction.tsx | 89 +++++++++++++++++++ .../useUserInfoActions/useUserInfoActions.ts | 39 +++++--- .../views/room/lib/getRoomDirectives.ts | 6 +- 4 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 apps/meteor/client/views/hooks/useMemberExists.ts create mode 100644 apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts new file mode 100644 index 000000000000..5487ad9dc887 --- /dev/null +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -0,0 +1,23 @@ +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; + +type MembersExistsOptions = { + rid: string; + username: string; + roomType: 'd' | 'p' | 'c'; + refresh?: boolean; +}; + +const endpointsByRoomType = { + d: '/v1/im.memberExists', + p: '/v1/groups.memberExists', + c: '/v1/channels.memberExists', +} as const; + +export const useMemberExists = (options: MembersExistsOptions) => { + const checkMember = useEndpoint('GET', endpointsByRoomType[options.roomType]); + + return useQuery(['roomMembershipCheck', options.rid, options.username], () => + checkMember({ roomId: options.rid, username: options.username }), + ); +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx new file mode 100644 index 000000000000..f4e70b0bff73 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx @@ -0,0 +1,89 @@ +import type { IRoom, IUser } from '@rocket.chat/core-typings'; +import { isRoomFederated } from '@rocket.chat/core-typings'; +import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; +import { + useTranslation, + useUser, + useUserRoom, + useUserSubscription, + useMethod, + useToastMessageDispatch, + useAtLeastOnePermission, +} from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import * as Federation from '../../../../../lib/federation/Federation'; +import { useAddMatrixUsers } from '../../../contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; +import type { UserInfoAction } from '../useUserInfoActions'; + +export const useAddUserAction = ( + user: Pick, + rid: IRoom['_id'], + reload?: () => void, +): UserInfoAction | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const currentUser = useUser(); + const subscription = useUserSubscription(rid); + const dispatchToastMessage = useToastMessageDispatch(); + + const { username, _id: uid } = user; + + if (!room) { + throw Error('Room not provided'); + } + + const hasPermissionToAddUsers = useAtLeastOnePermission( + useMemo(() => [room?.t === 'p' ? 'add-user-to-any-p-room' : 'add-user-to-any-c-room', 'add-user-to-joined-room'], [room?.t]), + rid, + ); + + const userCanAdd = + room && user && isRoomFederated(room) + ? Federation.isEditableByTheUser(currentUser || undefined, room, subscription) + : hasPermissionToAddUsers; + + const { roomCanInvite } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription }); + + const addUsersToRooms = useMethod('addUsersToRoom'); + + const handleAddUser = useEffectEvent(async ({ users }) => { + await addUsersToRooms({ rid, users }); + reload?.(); + }); + + const addClickHandler = useAddMatrixUsers(); + + const addUserOptionAction = useEffectEvent(async () => { + try { + const users = [username as string]; + if (isRoomFederated(room)) { + addClickHandler.mutate({ + users, + handleSave: handleAddUser, + }); + } else { + await handleAddUser({ users }); + } + dispatchToastMessage({ type: 'success', message: t('User_added') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error as Error }); + } + }); + + const addUserOption = useMemo( + () => + roomCanInvite && userCanAdd + ? { + content: t('Add_User'), + icon: 'user-plus' as const, + onClick: addUserOptionAction, + type: 'management' as const, + } + : undefined, + [roomCanInvite, userCanAdd, t, addUserOptionAction], + ); + + return addUserOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index a058fb862ad5..e159a9681f3f 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -1,11 +1,13 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; import type { Icon } from '@rocket.chat/fuselage'; -import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; +import { useLayoutHiddenActions, useUserRoom } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import { useEmbeddedLayout } from '../../../../hooks/useEmbeddedLayout'; +import { useMemberExists } from '../../../hooks/useMemberExists'; +import { useAddUserAction } from './actions/useAddUserAction'; import { useBlockUserAction } from './actions/useBlockUserAction'; import { useCallAction } from './actions/useCallAction'; import { useChangeLeaderAction } from './actions/useChangeLeaderAction'; @@ -34,12 +36,26 @@ type UserMenuAction = { items: GenericMenuItemProps[]; }[]; +type validRoomType = 'd' | 'p' | 'c'; + export const useUserInfoActions = ( user: Pick, rid: IRoom['_id'], reload?: () => void, size = 2, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { + const room = useUserRoom(rid); + + const { data, refetch } = useMemberExists( + useMemo(() => ({ rid, roomType: room?.t as validRoomType, username: user.username || '' }), [rid, room?.t, user?.username]), + ); + const memberChangeReload = useCallback(async () => { + await reload?.(); + await refetch(); + }, [reload, refetch]); + const isMember = !!useMemo(() => data?.exists, [data?.exists]); + + const addUser = useAddUserAction(user, rid, memberChangeReload); const blockUser = useBlockUserAction(user, rid); const changeLeader = useChangeLeaderAction(user, rid); const changeModerator = useChangeModeratorAction(user, rid); @@ -48,7 +64,7 @@ export const useUserInfoActions = ( const openDirectMessage = useDirectMessageAction(user, rid); const ignoreUser = useIgnoreUserAction(user, rid); const muteUser = useMuteUserAction(user, rid); - const removeUser = useRemoveUserAction(user, rid, reload); + const removeUser = useRemoveUserAction(user, rid, memberChangeReload); const call = useCallAction(user); const reportUserOption = useReportUser(user); const isLayoutEmbedded = useEmbeddedLayout(); @@ -58,15 +74,16 @@ export const useUserInfoActions = ( () => ({ ...(openDirectMessage && !isLayoutEmbedded && { openDirectMessage }), ...(call && { call }), - ...(changeOwner && { changeOwner }), - ...(changeLeader && { changeLeader }), - ...(changeModerator && { changeModerator }), - ...(openModerationConsole && { openModerationConsole }), - ...(ignoreUser && { ignoreUser }), - ...(muteUser && { muteUser }), + ...(!isMember && addUser && { addUser }), + ...(isMember && changeOwner && { changeOwner }), + ...(isMember && changeLeader && { changeLeader }), + ...(isMember && changeModerator && { changeModerator }), + ...(isMember && openModerationConsole && { openModerationConsole }), + ...(isMember && ignoreUser && { ignoreUser }), + ...(isMember && muteUser && { muteUser }), ...(blockUser && { toggleBlock: blockUser }), ...(reportUserOption && { reportUser: reportUserOption }), - ...(removeUser && { removeUser }), + ...(isMember && removeUser && { removeUser }), }), [ openDirectMessage, @@ -81,6 +98,8 @@ export const useUserInfoActions = ( removeUser, reportUserOption, openModerationConsole, + addUser, + isMember, ], ); diff --git a/apps/meteor/client/views/room/lib/getRoomDirectives.ts b/apps/meteor/client/views/room/lib/getRoomDirectives.ts index f03d41622606..f697fc7b51a6 100644 --- a/apps/meteor/client/views/room/lib/getRoomDirectives.ts +++ b/apps/meteor/client/views/room/lib/getRoomDirectives.ts @@ -11,6 +11,7 @@ type getRoomDirectiesType = { roomCanBlock: boolean; roomCanMute: boolean; roomCanRemove: boolean; + roomCanInvite: boolean; }; export const getRoomDirectives = ({ @@ -24,7 +25,7 @@ export const getRoomDirectives = ({ }): getRoomDirectiesType => { const roomDirectives = room?.t && roomCoordinator.getRoomDirectives(room.t); - const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove] = [ + const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite] = [ ...((roomDirectives && [ roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_OWNER, showingUserId, userSubscription), roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_LEADER, showingUserId, userSubscription), @@ -33,9 +34,10 @@ export const getRoomDirectives = ({ roomDirectives.allowMemberAction(room, RoomMemberActions.BLOCK, showingUserId, userSubscription), roomDirectives.allowMemberAction(room, RoomMemberActions.MUTE, showingUserId, userSubscription), roomDirectives.allowMemberAction(room, RoomMemberActions.REMOVE_USER, showingUserId, userSubscription), + roomDirectives.allowMemberAction(room, RoomMemberActions.INVITE, showingUserId, userSubscription), ]) ?? []), ]; - return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove }; + return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite }; }; From 2a58c4486d7652a79150ae3b3bc0e0f0d9598242 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 24 Jan 2024 18:43:56 +0530 Subject: [PATCH 04/39] added changeset --- .changeset/ninety-hounds-exist.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/ninety-hounds-exist.md diff --git a/.changeset/ninety-hounds-exist.md b/.changeset/ninety-hounds-exist.md new file mode 100644 index 000000000000..bdd3b9ed61c5 --- /dev/null +++ b/.changeset/ninety-hounds-exist.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/model-typings': patch +'@rocket.chat/rest-typings': patch +'@rocket.chat/meteor': patch +--- + +Fix: Show correct user info actions for non-members in channels. From f848c67a8ab1f924c4900bfcf62124c606c80db8 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 1 Feb 2024 17:48:48 +0530 Subject: [PATCH 05/39] added ajv validation --- apps/meteor/app/api/server/v1/channels.ts | 14 +++++-------- apps/meteor/app/api/server/v1/groups.ts | 3 ++- apps/meteor/app/api/server/v1/im.ts | 2 ++ .../v1/channels/ChannelsMemberExistsProps.ts | 21 +++++++++++++++++++ .../rest-typings/src/v1/channels/index.ts | 1 + .../src/v1/dm/DmMemberExistsProps.ts | 21 +++++++++++++++++++ packages/rest-typings/src/v1/dm/index.ts | 1 + .../src/v1/groups/GroupsMemberExistsProps.ts | 21 +++++++++++++++++++ packages/rest-typings/src/v1/groups/index.ts | 1 + 9 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts create mode 100644 packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts create mode 100644 packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 7ebe1adc72b5..8d05a4fc4506 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -19,6 +19,7 @@ import { isChannelsSetReadOnlyProps, isChannelsDeleteProps, isChannelsImagesProps, + isChannelsMemberExistsProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -1096,17 +1097,12 @@ API.v1.addRoute( API.v1.addRoute( 'channels.memberExists', - { authRequired: true }, + { + authRequired: true, + validateParams: isChannelsMemberExistsProps, + }, { async get() { - check( - this.queryParams, - Match.ObjectIncluding({ - username: String, - roomId: String, - }), - ); - const { username, roomId } = this.queryParams; const findResult = await findChannelByIdOrName({ diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index e29a102367d2..c125814c230f 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -1,6 +1,7 @@ import { Team, isMeteorError } from '@rocket.chat/core-services'; import type { IIntegration, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; +import { isGroupsMemberExistsProps } from '@rocket.chat/rest-typings'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { Filter } from 'mongodb'; @@ -749,7 +750,7 @@ API.v1.addRoute( API.v1.addRoute( 'groups.memberExists', - { authRequired: true }, + { authRequired: true, validateParams: isGroupsMemberExistsProps }, { async get() { check( diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index 30027b1b515d..8eaae12f73ce 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -10,6 +10,7 @@ import { isDmMessagesProps, isDmCreateProps, isDmHistoryProps, + isDmMemberExistsProps, } from '@rocket.chat/rest-typings'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -357,6 +358,7 @@ API.v1.addRoute( ['dm.memberExists', 'im.memberExists'], { authRequired: true, + validateParams: isDmMemberExistsProps, }, { async get() { diff --git a/packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts b/packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts new file mode 100644 index 000000000000..89466c743841 --- /dev/null +++ b/packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts @@ -0,0 +1,21 @@ +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type ChannelsMemberExistsProps = { roomId: string; username: string }; + +const channelsMemberExistsPropsSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + username: { + type: 'string', + }, + }, + required: ['roomId', 'username'], + additionalProperties: false, +}; + +export const isChannelsMemberExistsProps = ajv.compile(channelsMemberExistsPropsSchema); diff --git a/packages/rest-typings/src/v1/channels/index.ts b/packages/rest-typings/src/v1/channels/index.ts index f0cf81ba622d..9cb81d1e7470 100644 --- a/packages/rest-typings/src/v1/channels/index.ts +++ b/packages/rest-typings/src/v1/channels/index.ts @@ -18,3 +18,4 @@ export * from './ChannelsRolesProps'; export * from './ChannelsSetAnnouncementProps'; export * from './ChannelsSetReadOnlyProps'; export * from './ChannelsUnarchiveProps'; +export * from './ChannelsMemberExistsProps'; diff --git a/packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts b/packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts new file mode 100644 index 000000000000..64ab1a97e87f --- /dev/null +++ b/packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts @@ -0,0 +1,21 @@ +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type DmMemberExistsProps = { roomId: string; username: string }; + +const dmMemberExistsPropsSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + username: { + type: 'string', + }, + }, + required: ['roomId', 'username'], + additionalProperties: false, +}; + +export const isDmMemberExistsProps = ajv.compile(dmMemberExistsPropsSchema); diff --git a/packages/rest-typings/src/v1/dm/index.ts b/packages/rest-typings/src/v1/dm/index.ts index 1fc5233e8d3e..a802a7cbae29 100644 --- a/packages/rest-typings/src/v1/dm/index.ts +++ b/packages/rest-typings/src/v1/dm/index.ts @@ -5,3 +5,4 @@ export * from './DmDeleteProps'; export * from './DmFileProps'; export * from './DmMembersProps'; export * from './DmMessagesProps'; +export * from './DmMemberExistsProps'; diff --git a/packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts b/packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts new file mode 100644 index 000000000000..577bba7d4b22 --- /dev/null +++ b/packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts @@ -0,0 +1,21 @@ +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type GroupsMemberExistsProps = { roomId: string; username: string }; + +const groupsMemberExistsPropsSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + username: { + type: 'string', + }, + }, + required: ['roomId', 'username'], + additionalProperties: false, +}; + +export const isGroupsMemberExistsProps = ajv.compile(groupsMemberExistsPropsSchema); diff --git a/packages/rest-typings/src/v1/groups/index.ts b/packages/rest-typings/src/v1/groups/index.ts index 49907d3a08e5..3617e3a25470 100644 --- a/packages/rest-typings/src/v1/groups/index.ts +++ b/packages/rest-typings/src/v1/groups/index.ts @@ -37,3 +37,4 @@ export * from './GroupsSetTopicProps'; export * from './GroupsSetTypeProps'; export * from './GroupsModeratorsProps'; export * from './GroupsHistoryProps'; +export * from './GroupsMemberExistsProps'; From 8b070313ddc3db3343bd18c4ded1ecc31ff40e79 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 1 Feb 2024 18:57:25 +0530 Subject: [PATCH 06/39] remove check() --- apps/meteor/app/api/server/v1/groups.ts | 8 -------- apps/meteor/app/api/server/v1/im.ts | 8 -------- 2 files changed, 16 deletions(-) diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index c125814c230f..1d0900090521 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -753,14 +753,6 @@ API.v1.addRoute( { authRequired: true, validateParams: isGroupsMemberExistsProps }, { async get() { - check( - this.queryParams, - Match.ObjectIncluding({ - username: String, - roomId: String, - }), - ); - const { username, roomId } = this.queryParams; const findResult = await findPrivateGroupByIdOrName({ diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index 8eaae12f73ce..77949572a1cd 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -362,14 +362,6 @@ API.v1.addRoute( }, { async get() { - check( - this.queryParams, - Match.ObjectIncluding({ - username: String, - roomId: String, - }), - ); - const { username, roomId } = this.queryParams; const { room } = await findDirectMessageRoom({ roomId }, this.userId); From c1580f4bb96964e8bbf9b9ea3df340dd8bfa1cbb Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 8 Feb 2024 02:30:21 +0530 Subject: [PATCH 07/39] replaced memberExists endpoint with subscription.exists Signed-off-by: Abhinav Kumar --- .changeset/ninety-hounds-exist.md | 1 - apps/meteor/app/api/server/v1/channels.ts | 28 ----------- apps/meteor/app/api/server/v1/groups.ts | 25 ---------- apps/meteor/app/api/server/v1/im.ts | 32 ------------- .../meteor/app/api/server/v1/subscriptions.ts | 43 ++++++++++++++++- apps/meteor/server/models/raw/Users.js | 9 ---- .../model-typings/src/models/IUsersModel.ts | 1 - .../v1/channels/ChannelsMemberExistsProps.ts | 21 -------- .../rest-typings/src/v1/channels/channels.ts | 3 -- .../rest-typings/src/v1/channels/index.ts | 1 - .../src/v1/dm/DmMemberExistsProps.ts | 21 -------- packages/rest-typings/src/v1/dm/dm.ts | 1 - packages/rest-typings/src/v1/dm/im.ts | 4 -- packages/rest-typings/src/v1/dm/index.ts | 1 - .../src/v1/groups/GroupsMemberExistsProps.ts | 21 -------- packages/rest-typings/src/v1/groups/groups.ts | 3 -- packages/rest-typings/src/v1/groups/index.ts | 1 - .../src/v1/subscriptionsEndpoints.ts | 48 +++++++++++++++++++ 18 files changed, 90 insertions(+), 174 deletions(-) delete mode 100644 packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts delete mode 100644 packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts delete mode 100644 packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts diff --git a/.changeset/ninety-hounds-exist.md b/.changeset/ninety-hounds-exist.md index bdd3b9ed61c5..9b89cf2f1445 100644 --- a/.changeset/ninety-hounds-exist.md +++ b/.changeset/ninety-hounds-exist.md @@ -1,5 +1,4 @@ --- -'@rocket.chat/model-typings': patch '@rocket.chat/rest-typings': patch '@rocket.chat/meteor': patch --- diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 8d05a4fc4506..1c84926edb63 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -19,7 +19,6 @@ import { isChannelsSetReadOnlyProps, isChannelsDeleteProps, isChannelsImagesProps, - isChannelsMemberExistsProps, } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; @@ -1095,33 +1094,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'channels.memberExists', - { - authRequired: true, - validateParams: isChannelsMemberExistsProps, - }, - { - async get() { - const { username, roomId } = this.queryParams; - - const findResult = await findChannelByIdOrName({ - params: { roomId }, - checkedArchived: false, - }); - - if (findResult.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', findResult._id))) { - return API.v1.unauthorized(); - } - - const options = { projection: { username: 1 } }; - const user = await Users.findOneByUsernameAndRoom(username, findResult._id, options); - - return API.v1.success({ exists: !!user }); - }, - }, -); - API.v1.addRoute( 'channels.online', { authRequired: true }, diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts index 1d0900090521..34deb57304fc 100644 --- a/apps/meteor/app/api/server/v1/groups.ts +++ b/apps/meteor/app/api/server/v1/groups.ts @@ -1,7 +1,6 @@ import { Team, isMeteorError } from '@rocket.chat/core-services'; import type { IIntegration, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; -import { isGroupsMemberExistsProps } from '@rocket.chat/rest-typings'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { Filter } from 'mongodb'; @@ -748,30 +747,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'groups.memberExists', - { authRequired: true, validateParams: isGroupsMemberExistsProps }, - { - async get() { - const { username, roomId } = this.queryParams; - - const findResult = await findPrivateGroupByIdOrName({ - params: { roomId }, - userId: this.userId, - }); - - if (findResult.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', findResult.rid))) { - return API.v1.unauthorized(); - } - - const options = { projection: { username: 1 } }; - const user = await Users.findOneByUsernameAndRoom(username, findResult.rid, options); - - return API.v1.success({ exists: !!user }); - }, - }, -); - API.v1.addRoute( 'groups.messages', { authRequired: true }, diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index 77949572a1cd..a640318a9cd0 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -10,7 +10,6 @@ import { isDmMessagesProps, isDmCreateProps, isDmHistoryProps, - isDmMemberExistsProps, } from '@rocket.chat/rest-typings'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -354,37 +353,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - ['dm.memberExists', 'im.memberExists'], - { - authRequired: true, - validateParams: isDmMemberExistsProps, - }, - { - async get() { - const { username, roomId } = this.queryParams; - - const { room } = await findDirectMessageRoom({ roomId }, this.userId); - - const canAccess = await canAccessRoomIdAsync(room._id, this.userId); - if (!canAccess) { - return API.v1.unauthorized(); - } - - const query = { - _id: { $in: room.uids }, - username, - }; - - const options = { projection: { _id: 1 } }; - - const user = await Users.findOne(query, options); - - return API.v1.success({ exists: !!user }); - }, - }, -); - API.v1.addRoute( ['dm.messages', 'im.messages'], { diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index 9d81fe6bef65..003fd968a868 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -1,4 +1,4 @@ -import { Subscriptions } from '@rocket.chat/models'; +import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { isSubscriptionsGetProps, isSubscriptionsGetOneProps, @@ -10,6 +10,15 @@ import { Meteor } from 'meteor/meteor'; import { readMessages } from '../../../../server/lib/readMessages'; import { API } from '../api'; +import { hasAlreadyJoinedRoom } from '/server/services/authorization/hasAlreadyJoinedRoom'; + +import { Authorization } from '@rocket.chat/core-services'; + +import { hasPermissionAsync } from '/app/authorization/server/functions/hasPermission'; +import { canAccessRoomIdAsync } from '/app/authorization/server/functions/canAccessRoom'; +import { canAccessRoom } from '/server/services/authorization/canAccessRoom'; +import { isSubscriptionsExistsProps } from '@rocket.chat/rest-typings/src'; + API.v1.addRoute( 'subscriptions.get', { @@ -103,3 +112,35 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'subscriptions.exists', + { + authRequired: true, + validateParams: isSubscriptionsExistsProps, + }, + { + async get() { + const { username, roomId, userId, roomName } = this.queryParams; + const [room, user] = await Promise.all([ + Rooms.findOneByIdOrName(roomId || roomName), + Users.findOneByIdOrUsername(userId || username), + ]); + if (!room?._id) { + return API.v1.failure('error-room-not-found'); + } + if (!user?._id) { + return API.v1.failure('error-user-not-found'); + } + + if (room.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', room._id))) { + return API.v1.unauthorized(); + } + + if ((await canAccessRoom(room, this.user)) || (await canAccessRoomIdAsync(room._id, this.userId))) { + return API.v1.success({ exists: !!(await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) }); + } + return API.v1.unauthorized(); + }, + }, +); diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 622c530099e6..0d56fc76bccf 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -244,15 +244,6 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } - findOneByUsernameAndRoom(username, rid, options) { - const query = { - __rooms: rid, - username, - }; - - return this.findOne(query, options); - } - findOneByIdAndLoginHashedToken(_id, token, options = {}) { const query = { _id, diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 32599ed4ed6e..f9a2b1c45a2a 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -27,7 +27,6 @@ export interface IUsersModel extends IBaseModel { options: any, ): FindPaginated>; findOneByUsernameAndRoomIgnoringCase(username: string, rid: IRoom['_id'], options: any): FindCursor; - findOneByUsernameAndRoom(username: string, rid: IRoom['_id'], options: any): FindCursor; findOneByIdAndLoginHashedToken(_id: string, token: any, options?: any): FindCursor; findByActiveUsersExcept( searchTerm: any, diff --git a/packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts b/packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts deleted file mode 100644 index 89466c743841..000000000000 --- a/packages/rest-typings/src/v1/channels/ChannelsMemberExistsProps.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type ChannelsMemberExistsProps = { roomId: string; username: string }; - -const channelsMemberExistsPropsSchema = { - type: 'object', - properties: { - roomId: { - type: 'string', - }, - username: { - type: 'string', - }, - }, - required: ['roomId', 'username'], - additionalProperties: false, -}; - -export const isChannelsMemberExistsProps = ajv.compile(channelsMemberExistsPropsSchema); diff --git a/packages/rest-typings/src/v1/channels/channels.ts b/packages/rest-typings/src/v1/channels/channels.ts index 02379315f68d..328588ffb58b 100644 --- a/packages/rest-typings/src/v1/channels/channels.ts +++ b/packages/rest-typings/src/v1/channels/channels.ts @@ -53,9 +53,6 @@ export type ChannelsEndpoints = { members: IUser[]; }>; }; - '/v1/channels.memberExists': { - GET: (params: { roomId: string } | { roomName: string }) => { exists: boolean }; - }; '/v1/channels.history': { GET: (params: ChannelsHistoryProps) => PaginatedResult<{ messages: IMessage[]; diff --git a/packages/rest-typings/src/v1/channels/index.ts b/packages/rest-typings/src/v1/channels/index.ts index 9cb81d1e7470..f0cf81ba622d 100644 --- a/packages/rest-typings/src/v1/channels/index.ts +++ b/packages/rest-typings/src/v1/channels/index.ts @@ -18,4 +18,3 @@ export * from './ChannelsRolesProps'; export * from './ChannelsSetAnnouncementProps'; export * from './ChannelsSetReadOnlyProps'; export * from './ChannelsUnarchiveProps'; -export * from './ChannelsMemberExistsProps'; diff --git a/packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts b/packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts deleted file mode 100644 index 64ab1a97e87f..000000000000 --- a/packages/rest-typings/src/v1/dm/DmMemberExistsProps.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type DmMemberExistsProps = { roomId: string; username: string }; - -const dmMemberExistsPropsSchema = { - type: 'object', - properties: { - roomId: { - type: 'string', - }, - username: { - type: 'string', - }, - }, - required: ['roomId', 'username'], - additionalProperties: false, -}; - -export const isDmMemberExistsProps = ajv.compile(dmMemberExistsPropsSchema); diff --git a/packages/rest-typings/src/v1/dm/dm.ts b/packages/rest-typings/src/v1/dm/dm.ts index 2d04f31207c2..80955beeef04 100644 --- a/packages/rest-typings/src/v1/dm/dm.ts +++ b/packages/rest-typings/src/v1/dm/dm.ts @@ -8,7 +8,6 @@ export type DmEndpoints = { '/v1/dm.files': ImEndpoints['/v1/im.files']; '/v1/dm.history': ImEndpoints['/v1/im.history']; '/v1/dm.members': ImEndpoints['/v1/im.members']; - '/v1/dm.memberExists': ImEndpoints['/v1/im.memberExists']; '/v1/dm.messages': ImEndpoints['/v1/im.messages']; '/v1/dm.messages.others': ImEndpoints['/v1/im.messages.others']; '/v1/dm.list': ImEndpoints['/v1/im.list']; diff --git a/packages/rest-typings/src/v1/dm/im.ts b/packages/rest-typings/src/v1/dm/im.ts index 6b3d96e95025..6b98947eaf9b 100644 --- a/packages/rest-typings/src/v1/dm/im.ts +++ b/packages/rest-typings/src/v1/dm/im.ts @@ -57,10 +57,6 @@ export type ImEndpoints = { }>; }; - '/v1/im.memberExists': { - GET: (params: { username: string; roomId: string }) => { exists: boolean }; - }; - '/v1/im.messages': { GET: (params: DmMessagesProps) => PaginatedResult<{ messages: IMessage[]; diff --git a/packages/rest-typings/src/v1/dm/index.ts b/packages/rest-typings/src/v1/dm/index.ts index a802a7cbae29..1fc5233e8d3e 100644 --- a/packages/rest-typings/src/v1/dm/index.ts +++ b/packages/rest-typings/src/v1/dm/index.ts @@ -5,4 +5,3 @@ export * from './DmDeleteProps'; export * from './DmFileProps'; export * from './DmMembersProps'; export * from './DmMessagesProps'; -export * from './DmMemberExistsProps'; diff --git a/packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts b/packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts deleted file mode 100644 index 577bba7d4b22..000000000000 --- a/packages/rest-typings/src/v1/groups/GroupsMemberExistsProps.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type GroupsMemberExistsProps = { roomId: string; username: string }; - -const groupsMemberExistsPropsSchema = { - type: 'object', - properties: { - roomId: { - type: 'string', - }, - username: { - type: 'string', - }, - }, - required: ['roomId', 'username'], - additionalProperties: false, -}; - -export const isGroupsMemberExistsProps = ajv.compile(groupsMemberExistsPropsSchema); diff --git a/packages/rest-typings/src/v1/groups/groups.ts b/packages/rest-typings/src/v1/groups/groups.ts index 10fbb138e658..529e086a81af 100644 --- a/packages/rest-typings/src/v1/groups/groups.ts +++ b/packages/rest-typings/src/v1/groups/groups.ts @@ -53,9 +53,6 @@ export type GroupsEndpoints = { total: number; }; }; - '/v1/groups.memberExists': { - GET: (params: { roomId: string; roomName: string }) => { exists: boolean }; - }; '/v1/groups.history': { GET: (params: GroupsHistoryProps) => PaginatedResult<{ messages: IMessage[]; diff --git a/packages/rest-typings/src/v1/groups/index.ts b/packages/rest-typings/src/v1/groups/index.ts index 3617e3a25470..49907d3a08e5 100644 --- a/packages/rest-typings/src/v1/groups/index.ts +++ b/packages/rest-typings/src/v1/groups/index.ts @@ -37,4 +37,3 @@ export * from './GroupsSetTopicProps'; export * from './GroupsSetTypeProps'; export * from './GroupsModeratorsProps'; export * from './GroupsHistoryProps'; -export * from './GroupsMemberExistsProps'; diff --git a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts index 9122c4c84cca..ba442eac9790 100644 --- a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts +++ b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts @@ -9,6 +9,8 @@ type SubscriptionsRead = { rid: IRoom['_id']; readThreads?: boolean } | { roomId type SubscriptionsUnread = { roomId: IRoom['_id'] } | { firstUnreadMessage: Pick }; +type SubscriptionsExistsProps = { roomId: string; username: string }; + const ajv = new Ajv({ coerceTypes: true, }); @@ -109,6 +111,49 @@ const SubscriptionsUnreadSchema = { export const isSubscriptionsUnreadProps = ajv.compile(SubscriptionsUnreadSchema); +const SubscriptionsExistsPropsSchema = { + oneOf: [ + { + type: 'object', + properties: { + roomId: { type: 'string' }, + userId: { type: 'string' }, + }, + required: ['roomId', 'userId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + roomId: { type: 'string' }, + username: { type: 'string' }, + }, + required: ['roomId', 'username'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + roomName: { type: 'string' }, + userId: { type: 'string' }, + }, + required: ['roomName', 'userId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + roomName: { type: 'string' }, + username: { type: 'string' }, + }, + required: ['roomName', 'username'], + additionalProperties: false, + }, + ], +}; + +export const isSubscriptionsExistsProps = ajv.compile(SubscriptionsExistsPropsSchema); + export type SubscriptionsEndpoints = { '/v1/subscriptions.get': { GET: (params: SubscriptionsGet) => { @@ -130,4 +175,7 @@ export type SubscriptionsEndpoints = { '/v1/subscriptions.unread': { POST: (params: SubscriptionsUnread) => void; }; + '/v1/subscriptions.exists': { + GET: (params: SubscriptionsExistsProps) => { exists: boolean }; + }; }; From 0dc3079efa8d3c3f9030893cf307e738647aeaf8 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 8 Feb 2024 02:45:28 +0530 Subject: [PATCH 08/39] use subscriptions.exist endpoint Signed-off-by: Abhinav Kumar --- apps/meteor/app/api/server/v1/subscriptions.ts | 15 +++++---------- apps/meteor/client/views/hooks/useMemberExists.ts | 9 +-------- .../useUserInfoActions/useUserInfoActions.ts | 10 ++-------- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index 003fd968a868..46e9fdf79582 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -5,20 +5,15 @@ import { isSubscriptionsReadProps, isSubscriptionsUnreadProps, } from '@rocket.chat/rest-typings'; +import { isSubscriptionsExistsProps } from '@rocket.chat/rest-typings/src'; import { Meteor } from 'meteor/meteor'; import { readMessages } from '../../../../server/lib/readMessages'; +import { canAccessRoomAsync } from '../../../authorization/server'; +import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { API } from '../api'; -import { hasAlreadyJoinedRoom } from '/server/services/authorization/hasAlreadyJoinedRoom'; - -import { Authorization } from '@rocket.chat/core-services'; - -import { hasPermissionAsync } from '/app/authorization/server/functions/hasPermission'; -import { canAccessRoomIdAsync } from '/app/authorization/server/functions/canAccessRoom'; -import { canAccessRoom } from '/server/services/authorization/canAccessRoom'; -import { isSubscriptionsExistsProps } from '@rocket.chat/rest-typings/src'; - API.v1.addRoute( 'subscriptions.get', { @@ -137,7 +132,7 @@ API.v1.addRoute( return API.v1.unauthorized(); } - if ((await canAccessRoom(room, this.user)) || (await canAccessRoomIdAsync(room._id, this.userId))) { + if ((await canAccessRoomAsync(room, this.user)) || (await canAccessRoomIdAsync(room._id, this.userId))) { return API.v1.success({ exists: !!(await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) }); } return API.v1.unauthorized(); diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index 5487ad9dc887..f74207f30b0f 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -4,18 +4,11 @@ import { useQuery } from '@tanstack/react-query'; type MembersExistsOptions = { rid: string; username: string; - roomType: 'd' | 'p' | 'c'; refresh?: boolean; }; -const endpointsByRoomType = { - d: '/v1/im.memberExists', - p: '/v1/groups.memberExists', - c: '/v1/channels.memberExists', -} as const; - export const useMemberExists = (options: MembersExistsOptions) => { - const checkMember = useEndpoint('GET', endpointsByRoomType[options.roomType]); + const checkMember = useEndpoint('GET', '/v1/subscriptions.exists'); return useQuery(['roomMembershipCheck', options.rid, options.username], () => checkMember({ roomId: options.rid, username: options.username }), diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index e159a9681f3f..2e809d88ff38 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -1,6 +1,6 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; import type { Icon } from '@rocket.chat/fuselage'; -import { useLayoutHiddenActions, useUserRoom } from '@rocket.chat/ui-contexts'; +import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; import { useCallback, useMemo } from 'react'; @@ -36,19 +36,13 @@ type UserMenuAction = { items: GenericMenuItemProps[]; }[]; -type validRoomType = 'd' | 'p' | 'c'; - export const useUserInfoActions = ( user: Pick, rid: IRoom['_id'], reload?: () => void, size = 2, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { - const room = useUserRoom(rid); - - const { data, refetch } = useMemberExists( - useMemo(() => ({ rid, roomType: room?.t as validRoomType, username: user.username || '' }), [rid, room?.t, user?.username]), - ); + const { data, refetch } = useMemberExists(useMemo(() => ({ rid, username: user.username || '' }), [rid, user.username])); const memberChangeReload = useCallback(async () => { await reload?.(); await refetch(); From d78888b4b62ab982470d08865804b57858b297d9 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 16 Feb 2024 15:06:04 +0530 Subject: [PATCH 09/39] Update packages/rest-typings/src/v1/dm/im.ts Co-authored-by: Marcos Spessatto Defendi --- packages/rest-typings/src/v1/dm/im.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rest-typings/src/v1/dm/im.ts b/packages/rest-typings/src/v1/dm/im.ts index 6b98947eaf9b..60d08abc56b2 100644 --- a/packages/rest-typings/src/v1/dm/im.ts +++ b/packages/rest-typings/src/v1/dm/im.ts @@ -56,7 +56,6 @@ export type ImEndpoints = { members: Pick[]; }>; }; - '/v1/im.messages': { GET: (params: DmMessagesProps) => PaginatedResult<{ messages: IMessage[]; From 9e42919f6a2045e353c71b02fede6202b022351b Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 16 Feb 2024 15:06:33 +0530 Subject: [PATCH 10/39] Update apps/meteor/app/api/server/v1/subscriptions.ts Co-authored-by: Marcos Spessatto Defendi --- apps/meteor/app/api/server/v1/subscriptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index 46e9fdf79582..fe7a73dad07c 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -133,7 +133,7 @@ API.v1.addRoute( } if ((await canAccessRoomAsync(room, this.user)) || (await canAccessRoomIdAsync(room._id, this.userId))) { - return API.v1.success({ exists: !!(await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) }); + return API.v1.success({ exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0 }); } return API.v1.unauthorized(); }, From e126fc8ac3172951b99619e1dde65980aa4b3163 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 16 Feb 2024 15:07:44 +0530 Subject: [PATCH 11/39] Update apps/meteor/app/api/server/v1/subscriptions.ts Co-authored-by: Marcos Spessatto Defendi --- apps/meteor/app/api/server/v1/subscriptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index fe7a73dad07c..a0760edf0d95 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -132,7 +132,7 @@ API.v1.addRoute( return API.v1.unauthorized(); } - if ((await canAccessRoomAsync(room, this.user)) || (await canAccessRoomIdAsync(room._id, this.userId))) { + if ((await canAccessRoomAsync(room, this.user)))) { return API.v1.success({ exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0 }); } return API.v1.unauthorized(); From 9abf6c25d04961d54fc402537e1be5aa07ddaa2e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 16 Feb 2024 15:09:38 +0530 Subject: [PATCH 12/39] Update packages/rest-typings/src/v1/subscriptionsEndpoints.ts Co-authored-by: gabriellsh <40830821+gabriellsh@users.noreply.github.com> --- .../src/v1/subscriptionsEndpoints.ts | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts index ba442eac9790..89cc32aad06d 100644 --- a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts +++ b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts @@ -112,44 +112,17 @@ const SubscriptionsUnreadSchema = { export const isSubscriptionsUnreadProps = ajv.compile(SubscriptionsUnreadSchema); const SubscriptionsExistsPropsSchema = { - oneOf: [ - { - type: 'object', - properties: { - roomId: { type: 'string' }, - userId: { type: 'string' }, - }, - required: ['roomId', 'userId'], - additionalProperties: false, - }, - { + { type: 'object', properties: { roomId: { type: 'string' }, - username: { type: 'string' }, - }, - required: ['roomId', 'username'], - additionalProperties: false, - }, - { - type: 'object', - properties: { roomName: { type: 'string' }, userId: { type: 'string' }, - }, - required: ['roomName', 'userId'], - additionalProperties: false, - }, - { - type: 'object', - properties: { - roomName: { type: 'string' }, username: { type: 'string' }, }, - required: ['roomName', 'username'], + oneOf: [{required: ['roomId', 'userId']}, {required: ['roomName', 'userId']}, {required: ['roomId', 'username']}, {required: ['roomName', 'username']}], additionalProperties: false, }, - ], }; export const isSubscriptionsExistsProps = ajv.compile(SubscriptionsExistsPropsSchema); From 5143108b0c08f449744476150c8aa25b2e6bdf05 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 16 Feb 2024 15:09:58 +0530 Subject: [PATCH 13/39] Update apps/meteor/client/views/hooks/useMemberExists.ts Co-authored-by: gabriellsh <40830821+gabriellsh@users.noreply.github.com> --- apps/meteor/client/views/hooks/useMemberExists.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index f74207f30b0f..e262856fe503 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -10,7 +10,7 @@ type MembersExistsOptions = { export const useMemberExists = (options: MembersExistsOptions) => { const checkMember = useEndpoint('GET', '/v1/subscriptions.exists'); - return useQuery(['roomMembershipCheck', options.rid, options.username], () => + return useQuery(['subscriptions/exists', options.rid, options.username], () => checkMember({ roomId: options.rid, username: options.username }), ); }; From fafbaff0012396bfb21464df10d7f0dffb93f6aa Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 16 Feb 2024 17:58:42 +0530 Subject: [PATCH 14/39] minor changes and subscriptions.exists -> rooms.isMember Signed-off-by: Abhinav Kumar --- apps/meteor/app/api/server/v1/rooms.ts | 38 ++++++++++++++++++- .../meteor/app/api/server/v1/subscriptions.ts | 38 +------------------ .../client/views/hooks/useMemberExists.ts | 14 ++----- .../useUserInfoActions/useUserInfoActions.ts | 4 +- packages/rest-typings/src/v1/rooms.ts | 25 ++++++++++++ .../src/v1/subscriptionsEndpoints.ts | 21 ---------- 6 files changed, 67 insertions(+), 73 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index ae08dae04938..a9f8c3805711 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1,8 +1,8 @@ import { Media } from '@rocket.chat/core-services'; import type { IRoom } from '@rocket.chat/core-typings'; -import { Messages, Rooms, Users } from '@rocket.chat/models'; +import { Messages, Rooms, Users, Subscriptions } from '@rocket.chat/models'; import type { Notifications } from '@rocket.chat/rest-typings'; -import { isGETRoomsNameExists } from '@rocket.chat/rest-typings'; +import { isGETRoomsNameExists, isRoomsIsMemberProps } from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../../lib/isTruthy'; @@ -635,3 +635,37 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'rooms.isMember', + { + authRequired: true, + validateParams: isRoomsIsMemberProps, + }, + { + async get() { + const { username, roomId, userId, roomName } = this.queryParams; + const [room, user] = await Promise.all([ + Rooms.findOneByIdOrName(roomId || roomName), + Users.findOneByIdOrUsername(userId || username), + ]); + if (!room?._id) { + return API.v1.failure('error-room-not-found'); + } + if (!user?._id) { + return API.v1.failure('error-user-not-found'); + } + + if (room.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', room._id))) { + return API.v1.unauthorized(); + } + + if (await canAccessRoomAsync(room, this.user)) { + return API.v1.success({ + exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0, + }); + } + return API.v1.unauthorized(); + }, + }, +); diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index a0760edf0d95..9d81fe6bef65 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -1,17 +1,13 @@ -import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; +import { Subscriptions } from '@rocket.chat/models'; import { isSubscriptionsGetProps, isSubscriptionsGetOneProps, isSubscriptionsReadProps, isSubscriptionsUnreadProps, } from '@rocket.chat/rest-typings'; -import { isSubscriptionsExistsProps } from '@rocket.chat/rest-typings/src'; import { Meteor } from 'meteor/meteor'; import { readMessages } from '../../../../server/lib/readMessages'; -import { canAccessRoomAsync } from '../../../authorization/server'; -import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { API } from '../api'; API.v1.addRoute( @@ -107,35 +103,3 @@ API.v1.addRoute( }, }, ); - -API.v1.addRoute( - 'subscriptions.exists', - { - authRequired: true, - validateParams: isSubscriptionsExistsProps, - }, - { - async get() { - const { username, roomId, userId, roomName } = this.queryParams; - const [room, user] = await Promise.all([ - Rooms.findOneByIdOrName(roomId || roomName), - Users.findOneByIdOrUsername(userId || username), - ]); - if (!room?._id) { - return API.v1.failure('error-room-not-found'); - } - if (!user?._id) { - return API.v1.failure('error-user-not-found'); - } - - if (room.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', room._id))) { - return API.v1.unauthorized(); - } - - if ((await canAccessRoomAsync(room, this.user)))) { - return API.v1.success({ exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0 }); - } - return API.v1.unauthorized(); - }, - }, -); diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index e262856fe503..a87f63ccda5f 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -1,16 +1,8 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -type MembersExistsOptions = { - rid: string; - username: string; - refresh?: boolean; -}; - -export const useMemberExists = (options: MembersExistsOptions) => { - const checkMember = useEndpoint('GET', '/v1/subscriptions.exists'); +export const useMemberExists = (roomId: string, username: string) => { + const checkMember = useEndpoint('GET', '/v1/rooms.isMember'); - return useQuery(['subscriptions/exists', options.rid, options.username], () => - checkMember({ roomId: options.rid, username: options.username }), - ); + return useQuery(['subscriptions/exists', roomId, username], () => checkMember({ roomId, username })); }; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index 2e809d88ff38..48518aff66b5 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -42,12 +42,12 @@ export const useUserInfoActions = ( reload?: () => void, size = 2, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { - const { data, refetch } = useMemberExists(useMemo(() => ({ rid, username: user.username || '' }), [rid, user.username])); + const { data, refetch } = useMemberExists(rid, user.username as string); const memberChangeReload = useCallback(async () => { await reload?.(); await refetch(); }, [reload, refetch]); - const isMember = !!useMemo(() => data?.exists, [data?.exists]); + const isMember = data?.exists as boolean; const addUser = useAddUserAction(user, rid, memberChangeReload); const blockUser = useBlockUserAction(user, rid); diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 8d6a7476d939..b191fcd35193 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -423,6 +423,27 @@ const GETRoomsNameExistsSchema = { export const isGETRoomsNameExists = ajv.compile(GETRoomsNameExistsSchema); +type RoomsIsMemberProps = ({ roomId: string } | { roomName: string }) & ({ username: string } | { userId: string }); + +const RoomsIsMemberPropsSchema = { + type: 'object', + properties: { + roomId: { type: 'string' }, + roomName: { type: 'string' }, + userId: { type: 'string' }, + username: { type: 'string' }, + }, + oneOf: [ + { required: ['roomId', 'userId'] }, + { required: ['roomName', 'userId'] }, + { required: ['roomId', 'username'] }, + { required: ['roomName', 'username'] }, + ], + additionalProperties: false, +}; + +export const isRoomsIsMemberProps = ajv.compile(RoomsIsMemberPropsSchema); + export type Notifications = { disableNotifications: string; muteGroupMentions: string; @@ -572,4 +593,8 @@ export type RoomsEndpoints = { discussions: IRoom[]; }>; }; + + '/v1/rooms.isMember': { + GET: (params: RoomsIsMemberProps) => { exists: boolean }; + }; }; diff --git a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts index 89cc32aad06d..9122c4c84cca 100644 --- a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts +++ b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts @@ -9,8 +9,6 @@ type SubscriptionsRead = { rid: IRoom['_id']; readThreads?: boolean } | { roomId type SubscriptionsUnread = { roomId: IRoom['_id'] } | { firstUnreadMessage: Pick }; -type SubscriptionsExistsProps = { roomId: string; username: string }; - const ajv = new Ajv({ coerceTypes: true, }); @@ -111,22 +109,6 @@ const SubscriptionsUnreadSchema = { export const isSubscriptionsUnreadProps = ajv.compile(SubscriptionsUnreadSchema); -const SubscriptionsExistsPropsSchema = { - { - type: 'object', - properties: { - roomId: { type: 'string' }, - roomName: { type: 'string' }, - userId: { type: 'string' }, - username: { type: 'string' }, - }, - oneOf: [{required: ['roomId', 'userId']}, {required: ['roomName', 'userId']}, {required: ['roomId', 'username']}, {required: ['roomName', 'username']}], - additionalProperties: false, - }, -}; - -export const isSubscriptionsExistsProps = ajv.compile(SubscriptionsExistsPropsSchema); - export type SubscriptionsEndpoints = { '/v1/subscriptions.get': { GET: (params: SubscriptionsGet) => { @@ -148,7 +130,4 @@ export type SubscriptionsEndpoints = { '/v1/subscriptions.unread': { POST: (params: SubscriptionsUnread) => void; }; - '/v1/subscriptions.exists': { - GET: (params: SubscriptionsExistsProps) => { exists: boolean }; - }; }; From b663025652a7f0ec4ae400dc0162800a6ba6af49 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sat, 17 Feb 2024 02:27:58 +0530 Subject: [PATCH 15/39] unit test for rooms.isMember Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 221 +++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index c717e526d20c..31dcff48949f 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1707,4 +1707,225 @@ describe('[Rooms]', function () { .end(done); }); }); + describe('/rooms.isMember', async () => { + let testChannel; + let testGroup; + let testDM; + + const fakeRoomId = `room.test.${Date.now()}-${Math.random()}`; + const fakeUserId = `user.test.${Date.now()}-${Math.random()}`; + + const testChannelName = `channel.test.${Date.now()}-${Math.random()}`; + const testGroupName = `group.test.${Date.now()}-${Math.random()}`; + + let testUser1; + let testUser2; + let testUserNonMember; + let testUser1Credentials; + let testUserNonMemberCredentials; + + it('create users', async () => { + testUser1 = await createUser(); + testUser2 = await createUser(); + testUserNonMember = await createUser(); + testUser1Credentials = await login(testUser1.username, password); + testUserNonMemberCredentials = await login(testUserNonMember.username, password); + }); + + it('create a channel', (done) => { + createRoom({ + type: 'c', + name: testChannelName, + members: [testUser1.username, testUser2.username], + }).end((err, res) => { + testChannel = res.body.channel; + done(); + }); + }); + it('create a group', (done) => { + createRoom({ + type: 'p', + name: testGroupName, + members: [testUser1.username, testUser2.username], + }).end((err, res) => { + testGroup = res.body.group; + done(); + }); + }); + it('create a direct message room', (done) => { + createRoom({ + type: 'd', + username: testUser2.username, + credentials: testUser1Credentials, + }).end((err, res) => { + testDM = res.body.room; + done(); + }); + }); + after(async () => { + await closeRoom({ type: 'c', roomId: testChannel._id }); + await closeRoom({ type: 'p', roomId: testGroup._id }); + await closeRoom({ type: 'd', roomId: testDM._id }); + }); + + it('should return error if room not found', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: fakeRoomId, + userId: testUser1._id, + }) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'error-room-not-found'); + }) + .end(done); + }); + + it('should return error if user not found', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testChannel._id, + userId: fakeUserId, + }) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'error-user-not-found'); + }) + .end(done); + }); + + it('should return success with exists=true if user is a member of the channel', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testChannel._id, + userId: testUser2._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', true); + }) + .end(done); + }); + + it('should return success with exists=false if user is not a member of the channel', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testChannel._id, + userId: testUserNonMember._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', false); + }) + .end(done); + }); + + it('should return success with exists=true if user is a member of the group', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testGroup._id, + userId: testUser2._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', true); + }) + .end(done); + }); + + it('should return success with exists=false if user is not a member of the group', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testGroup._id, + userId: testUserNonMember._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', false); + }) + .end(done); + }); + + it('should return unauthorized if caller cannot access the group', (done) => { + request + .get(api('rooms.isMember')) + .set(testUserNonMemberCredentials) + .query({ + roomId: testGroup._id, + userId: testUser1._id, + }) + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'unauthorized'); + }) + .end(done); + }); + + it('should return success with exists=true if user is a member of the DM', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testDM._id, + userId: testUser2._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', true); + }) + .end(done); + }); + + it('should return success with exists=false if user is not a member of the DM', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testDM._id, + userId: testUserNonMember._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', false); + }) + .end(done); + }); + + it('should return unauthorized if caller cannot access the DM', (done) => { + request + .get(api('rooms.isMember')) + .set(testUserNonMemberCredentials) + .query({ + roomId: testDM._id, + userId: testUser1._id, + }) + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'unauthorized'); + }) + .end(done); + }); + }); }); From c828dd99635e7b8b975d8ee680a57fb3eda610fb Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sun, 18 Feb 2024 02:24:46 +0530 Subject: [PATCH 16/39] use api endpoint Signed-off-by: Abhinav Kumar --- .../useUserInfoActions/actions/useAddUserAction.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx index f4e70b0bff73..9c4b4e839004 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx @@ -6,9 +6,9 @@ import { useUser, useUserRoom, useUserSubscription, - useMethod, useToastMessageDispatch, useAtLeastOnePermission, + useEndpoint, } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; @@ -17,6 +17,11 @@ import { useAddMatrixUsers } from '../../../contextualBar/RoomMembers/AddUsers/A import { getRoomDirectives } from '../../../lib/getRoomDirectives'; import type { UserInfoAction } from '../useUserInfoActions'; +const inviteUserEndpoints = { + c: '/v1/channels.invite', + p: '/v1/groups.invite', +} as const; + export const useAddUserAction = ( user: Pick, rid: IRoom['_id'], @@ -46,10 +51,11 @@ export const useAddUserAction = ( const { roomCanInvite } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription }); - const addUsersToRooms = useMethod('addUsersToRoom'); + const inviteUser = useEndpoint('POST', inviteUserEndpoints[room.t === 'p' ? 'p' : 'c']); const handleAddUser = useEffectEvent(async ({ users }) => { - await addUsersToRooms({ rid, users }); + const [username] = users; + await inviteUser({ roomId: rid, username }); reload?.(); }); From 0cc0471f8664ad4cfae623a871742898333bee2f Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sun, 18 Feb 2024 02:37:06 +0530 Subject: [PATCH 17/39] improve api test Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 31dcff48949f..c2c2f19a93e7 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1725,11 +1725,11 @@ describe('[Rooms]', function () { let testUserNonMemberCredentials; it('create users', async () => { - testUser1 = await createUser(); - testUser2 = await createUser(); - testUserNonMember = await createUser(); - testUser1Credentials = await login(testUser1.username, password); - testUserNonMemberCredentials = await login(testUserNonMember.username, password); + [testUser1, testUser2, testUserNonMember] = await Promise.all([createUser(), createUser(), createUser()]); + [testUser1Credentials, testUserNonMemberCredentials] = await Promise.all([ + login(testUser1.username, password), + login(testUserNonMember.username, password), + ]); }); it('create a channel', (done) => { @@ -1763,9 +1763,14 @@ describe('[Rooms]', function () { }); }); after(async () => { - await closeRoom({ type: 'c', roomId: testChannel._id }); - await closeRoom({ type: 'p', roomId: testGroup._id }); - await closeRoom({ type: 'd', roomId: testDM._id }); + await Promise.all([ + closeRoom({ type: 'c', roomId: testChannel._id }), + closeRoom({ type: 'p', roomId: testGroup._id }), + closeRoom({ type: 'd', roomId: testDM._id }), + deleteUser(testUser1), + deleteUser(testUser2), + deleteUser(testUserNonMember), + ]); }); it('should return error if room not found', (done) => { From 8aa68fcce1a566bf1b01bf2f4f5bdf7657af6ab7 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 20 Feb 2024 02:33:55 +0530 Subject: [PATCH 18/39] updated message for add to room userinfo action Signed-off-by: Abhinav Kumar --- .changeset/ninety-hounds-exist.md | 1 + .../room/hooks/useUserInfoActions/actions/useAddUserAction.tsx | 2 +- apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.changeset/ninety-hounds-exist.md b/.changeset/ninety-hounds-exist.md index 9b89cf2f1445..99882de12018 100644 --- a/.changeset/ninety-hounds-exist.md +++ b/.changeset/ninety-hounds-exist.md @@ -1,6 +1,7 @@ --- '@rocket.chat/rest-typings': patch '@rocket.chat/meteor': patch +'@rocket.chat/i18n': patch --- Fix: Show correct user info actions for non-members in channels. diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx index 9c4b4e839004..f93c3a433009 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx @@ -82,7 +82,7 @@ export const useAddUserAction = ( () => roomCanInvite && userCanAdd ? { - content: t('Add_User'), + content: t('add-to-room'), icon: 'user-plus' as const, onClick: addUserOptionAction, type: 'management' as const, diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index d9faf22baf4c..6fce3a71188e 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -321,6 +321,7 @@ "Add_User": "Add User", "Add_users": "Add users", "Add_members": "Add Members", + "add-to-room": "Add to room", "add-all-to-room": "Add all users to a room", "add-all-to-room_description": "Permission to add all users to a room", "add-livechat-department-agents": "Add Omnichannel Agents to Departments", @@ -6293,4 +6294,4 @@ "Seat_limit_reached": "Seat limit reached", "Seat_limit_reached_Description": "Your workspace reached its contractual seat limit. Buy more seats to add more users.", "Buy_more_seats": "Buy more seats" -} \ No newline at end of file +} From db5239511752db841b5cf72a17388d6b9b37f2a8 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 21 Feb 2024 20:23:45 +0530 Subject: [PATCH 19/39] added tests Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 66 ++++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index c2c2f19a93e7..979f7517791d 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -8,8 +8,14 @@ import { sleep } from '../../../lib/utils/sleep'; import { getCredentials, api, request, credentials } from '../../data/api-data.js'; import { sendSimpleMessage, deleteMessage } from '../../data/chat.helper'; import { imgURL } from '../../data/interactions'; -import { updateEEPermission, updatePermission, updateSetting } from '../../data/permissions.helper'; -import { closeRoom, createRoom } from '../../data/rooms.helper'; +import { + removePermissionFromAllRoles, + restorePermissionToRoles, + updateEEPermission, + updatePermission, + updateSetting, +} from '../../data/permissions.helper'; +import { closeRoom, createRoom, deleteRoom } from '../../data/rooms.helper'; import { password } from '../../data/user'; import { createUser, deleteUser, login } from '../../data/users.helper'; import { IS_EE } from '../../e2e/config/constants'; @@ -1762,16 +1768,17 @@ describe('[Rooms]', function () { done(); }); }); - after(async () => { - await Promise.all([ - closeRoom({ type: 'c', roomId: testChannel._id }), - closeRoom({ type: 'p', roomId: testGroup._id }), - closeRoom({ type: 'd', roomId: testDM._id }), + after(() => + Promise.all([ + deleteRoom({ type: 'c', roomId: testChannel._id }), + deleteRoom({ type: 'p', roomId: testGroup._id }), + deleteRoom({ type: 'd', roomId: testDM._id }), deleteUser(testUser1), deleteUser(testUser2), deleteUser(testUserNonMember), - ]); - }); + restorePermissionToRoles('view-broadcast-member-list'), + ]), + ); it('should return error if room not found', (done) => { request @@ -1784,12 +1791,15 @@ describe('[Rooms]', function () { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'error-room-not-found'); + expect(res.body).to.have.property( + 'error', + 'The required "roomId" or "roomName" param provided does not match any channel [error-room-not-found]', + ); }) .end(done); }); - it('should return error if user not found', (done) => { + it('should return error if user not found with the given userId', (done) => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1805,6 +1815,22 @@ describe('[Rooms]', function () { .end(done); }); + it('should return error if user not found with the given username', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testChannel._id, + username: fakeUserId, + }) + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'error-user-not-found'); + }) + .end(done); + }); + it('should return success with exists=true if user is a member of the channel', (done) => { request .get(api('rooms.isMember')) @@ -1932,5 +1958,23 @@ describe('[Rooms]', function () { }) .end(done); }); + + it('should return unauthorized if caller does not have view-broadcast-member-list permission', (done) => { + removePermissionFromAllRoles('view-broadcast-member-list').then(() => { + request + .get(api('rooms.isMember')) + .set(testUserNonMemberCredentials) + .query({ + roomId: testDM._id, + userId: testUser1._id, + }) + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'unauthorized'); + }) + .end(done); + }); + }); }); }); From 8c363e28b6e45af808b6951b6ee3d725ed2e370c Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 22 Feb 2024 02:16:51 +0530 Subject: [PATCH 20/39] updated rooms.isMember endpoint and its test --- apps/meteor/app/api/server/v1/rooms.ts | 14 ++-- .../actions/useAddUserAction.tsx | 4 +- .../useUserInfoActions/useUserInfoActions.ts | 22 ++--- apps/meteor/tests/end-to-end/api/09-rooms.js | 81 ++++++++++++------- packages/rest-typings/src/v1/rooms.ts | 10 +-- 5 files changed, 72 insertions(+), 59 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index a9f8c3805711..a8dd1f73b842 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -644,22 +644,18 @@ API.v1.addRoute( }, { async get() { - const { username, roomId, userId, roomName } = this.queryParams; + const { roomId, userId, username } = this.queryParams; const [room, user] = await Promise.all([ - Rooms.findOneByIdOrName(roomId || roomName), + findRoomByIdOrName({ + params: { roomId }, + }) as Promise, Users.findOneByIdOrUsername(userId || username), ]); - if (!room?._id) { - return API.v1.failure('error-room-not-found'); - } + if (!user?._id) { return API.v1.failure('error-user-not-found'); } - if (room.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', room._id))) { - return API.v1.unauthorized(); - } - if (await canAccessRoomAsync(room, this.user)) { return API.v1.success({ exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0, diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx index f93c3a433009..42388b100d41 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx @@ -80,7 +80,7 @@ export const useAddUserAction = ( const addUserOption = useMemo( () => - roomCanInvite && userCanAdd + roomCanInvite && userCanAdd && room.archived !== true ? { content: t('add-to-room'), icon: 'user-plus' as const, @@ -88,7 +88,7 @@ export const useAddUserAction = ( type: 'management' as const, } : undefined, - [roomCanInvite, userCanAdd, t, addUserOptionAction], + [roomCanInvite, userCanAdd, room.archived, t, addUserOptionAction], ); return addUserOption; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index 48518aff66b5..59707b59cf25 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -42,12 +42,12 @@ export const useUserInfoActions = ( reload?: () => void, size = 2, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { - const { data, refetch } = useMemberExists(rid, user.username as string); + const { data, refetch, isSuccess: membershipCheckSuccess } = useMemberExists(rid, user.username as string); const memberChangeReload = useCallback(async () => { await reload?.(); await refetch(); }, [reload, refetch]); - const isMember = data?.exists as boolean; + const showAddMember = (data?.exists as boolean) && membershipCheckSuccess; const addUser = useAddUserAction(user, rid, memberChangeReload); const blockUser = useBlockUserAction(user, rid); @@ -68,16 +68,16 @@ export const useUserInfoActions = ( () => ({ ...(openDirectMessage && !isLayoutEmbedded && { openDirectMessage }), ...(call && { call }), - ...(!isMember && addUser && { addUser }), - ...(isMember && changeOwner && { changeOwner }), - ...(isMember && changeLeader && { changeLeader }), - ...(isMember && changeModerator && { changeModerator }), - ...(isMember && openModerationConsole && { openModerationConsole }), - ...(isMember && ignoreUser && { ignoreUser }), - ...(isMember && muteUser && { muteUser }), + ...(!showAddMember && addUser && { addUser }), + ...(showAddMember && changeOwner && { changeOwner }), + ...(showAddMember && changeLeader && { changeLeader }), + ...(showAddMember && changeModerator && { changeModerator }), + ...(showAddMember && openModerationConsole && { openModerationConsole }), + ...(showAddMember && ignoreUser && { ignoreUser }), + ...(showAddMember && muteUser && { muteUser }), ...(blockUser && { toggleBlock: blockUser }), ...(reportUserOption && { reportUser: reportUserOption }), - ...(isMember && removeUser && { removeUser }), + ...(showAddMember && removeUser && { removeUser }), }), [ openDirectMessage, @@ -93,7 +93,7 @@ export const useUserInfoActions = ( reportUserOption, openModerationConsole, addUser, - isMember, + showAddMember, ], ); diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 979f7517791d..39aa39ed5c12 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -8,13 +8,7 @@ import { sleep } from '../../../lib/utils/sleep'; import { getCredentials, api, request, credentials } from '../../data/api-data.js'; import { sendSimpleMessage, deleteMessage } from '../../data/chat.helper'; import { imgURL } from '../../data/interactions'; -import { - removePermissionFromAllRoles, - restorePermissionToRoles, - updateEEPermission, - updatePermission, - updateSetting, -} from '../../data/permissions.helper'; +import { updateEEPermission, updatePermission, updateSetting } from '../../data/permissions.helper'; import { closeRoom, createRoom, deleteRoom } from '../../data/rooms.helper'; import { password } from '../../data/user'; import { createUser, deleteUser, login } from '../../data/users.helper'; @@ -1776,7 +1770,6 @@ describe('[Rooms]', function () { deleteUser(testUser1), deleteUser(testUser2), deleteUser(testUserNonMember), - restorePermissionToRoles('view-broadcast-member-list'), ]), ); @@ -1831,7 +1824,7 @@ describe('[Rooms]', function () { .end(done); }); - it('should return success with exists=true if user is a member of the channel', (done) => { + it('should return success with exists=true if given userId is a member of the channel', (done) => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1847,6 +1840,22 @@ describe('[Rooms]', function () { .end(done); }); + it('should return success with exists=true if given username is a member of the channel', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testChannel._id, + userId: testUser2.username, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', true); + }) + .end(done); + }); + it('should return success with exists=false if user is not a member of the channel', (done) => { request .get(api('rooms.isMember')) @@ -1863,7 +1872,7 @@ describe('[Rooms]', function () { .end(done); }); - it('should return success with exists=true if user is a member of the group', (done) => { + it('should return success with exists=true if given userId is a member of the group', (done) => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1879,6 +1888,22 @@ describe('[Rooms]', function () { .end(done); }); + it('should return success with exists=true if given username is a member of the group', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testGroup._id, + userId: testUser2.username, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', true); + }) + .end(done); + }); + it('should return success with exists=false if user is not a member of the group', (done) => { request .get(api('rooms.isMember')) @@ -1911,7 +1936,7 @@ describe('[Rooms]', function () { .end(done); }); - it('should return success with exists=true if user is a member of the DM', (done) => { + it('should return success with exists=true if given userId is a member of the DM', (done) => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1927,6 +1952,22 @@ describe('[Rooms]', function () { .end(done); }); + it('should return success with exists=true if given username is a member of the DM', (done) => { + request + .get(api('rooms.isMember')) + .set(testUser1Credentials) + .query({ + roomId: testDM._id, + userId: testUser2.username, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('exists', true); + }) + .end(done); + }); + it('should return success with exists=false if user is not a member of the DM', (done) => { request .get(api('rooms.isMember')) @@ -1958,23 +1999,5 @@ describe('[Rooms]', function () { }) .end(done); }); - - it('should return unauthorized if caller does not have view-broadcast-member-list permission', (done) => { - removePermissionFromAllRoles('view-broadcast-member-list').then(() => { - request - .get(api('rooms.isMember')) - .set(testUserNonMemberCredentials) - .query({ - roomId: testDM._id, - userId: testUser1._id, - }) - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); - }); - }); }); }); diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index b191fcd35193..9f8593e801ab 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -423,22 +423,16 @@ const GETRoomsNameExistsSchema = { export const isGETRoomsNameExists = ajv.compile(GETRoomsNameExistsSchema); -type RoomsIsMemberProps = ({ roomId: string } | { roomName: string }) & ({ username: string } | { userId: string }); +type RoomsIsMemberProps = { roomId: string } & ({ username: string } | { userId: string }); const RoomsIsMemberPropsSchema = { type: 'object', properties: { roomId: { type: 'string' }, - roomName: { type: 'string' }, userId: { type: 'string' }, username: { type: 'string' }, }, - oneOf: [ - { required: ['roomId', 'userId'] }, - { required: ['roomName', 'userId'] }, - { required: ['roomId', 'username'] }, - { required: ['roomName', 'username'] }, - ], + oneOf: [{ required: ['roomId', 'userId'] }, { required: ['roomId', 'username'] }], additionalProperties: false, }; From cb611193b0f0e0ea2d8df5e5199c7a1642994f3c Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 22 Feb 2024 20:10:36 +0530 Subject: [PATCH 21/39] updated test 09-rooms.js Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 29 +++++++++----------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 39aa39ed5c12..ba50980aa614 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1724,7 +1724,7 @@ describe('[Rooms]', function () { let testUser1Credentials; let testUserNonMemberCredentials; - it('create users', async () => { + before(async () => { [testUser1, testUser2, testUserNonMember] = await Promise.all([createUser(), createUser(), createUser()]); [testUser1Credentials, testUserNonMemberCredentials] = await Promise.all([ login(testUser1.username, password), @@ -1732,36 +1732,33 @@ describe('[Rooms]', function () { ]); }); - it('create a channel', (done) => { - createRoom({ + before(async () => { + const response = await createRoom({ type: 'c', name: testChannelName, members: [testUser1.username, testUser2.username], - }).end((err, res) => { - testChannel = res.body.channel; - done(); }); + testChannel = response.body.channel; }); - it('create a group', (done) => { - createRoom({ + + before(async () => { + const response = await createRoom({ type: 'p', name: testGroupName, members: [testUser1.username, testUser2.username], - }).end((err, res) => { - testGroup = res.body.group; - done(); }); + testGroup = response.body.group; }); - it('create a direct message room', (done) => { - createRoom({ + + before(async () => { + const response = await createRoom({ type: 'd', username: testUser2.username, credentials: testUser1Credentials, - }).end((err, res) => { - testDM = res.body.room; - done(); }); + testDM = response.body.room; }); + after(() => Promise.all([ deleteRoom({ type: 'c', roomId: testChannel._id }), From 5386bf4269359735f9e6e96484ff80745df89383 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 23 Feb 2024 00:53:30 +0530 Subject: [PATCH 22/39] Update useMemberExists.ts Co-authored-by: gabriellsh <40830821+gabriellsh@users.noreply.github.com> --- apps/meteor/client/views/hooks/useMemberExists.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index a87f63ccda5f..c66cff15509e 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -4,5 +4,5 @@ import { useQuery } from '@tanstack/react-query'; export const useMemberExists = (roomId: string, username: string) => { const checkMember = useEndpoint('GET', '/v1/rooms.isMember'); - return useQuery(['subscriptions/exists', roomId, username], () => checkMember({ roomId, username })); + return useQuery(['rooms/isMember', roomId, username], () => checkMember({ roomId, username })); }; From 3db764dbd14c08b282c8e93a76cd049a0b4f51ec Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 23 Feb 2024 16:06:34 +0530 Subject: [PATCH 23/39] minor fixes Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 70 ++++++++------------ 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index ba50980aa614..7079fe063c41 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1770,7 +1770,7 @@ describe('[Rooms]', function () { ]), ); - it('should return error if room not found', (done) => { + it('should return error if room not found', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1785,11 +1785,10 @@ describe('[Rooms]', function () { 'error', 'The required "roomId" or "roomName" param provided does not match any channel [error-room-not-found]', ); - }) - .end(done); + }); }); - it('should return error if user not found with the given userId', (done) => { + it('should return error if user not found with the given userId', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1801,11 +1800,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'error-user-not-found'); - }) - .end(done); + }); }); - it('should return error if user not found with the given username', (done) => { + it('should return error if user not found with the given username', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1817,11 +1815,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'error-user-not-found'); - }) - .end(done); + }); }); - it('should return success with exists=true if given userId is a member of the channel', (done) => { + it('should return success with exists=true if given userId is a member of the channel', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1833,11 +1830,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', true); - }) - .end(done); + }); }); - it('should return success with exists=true if given username is a member of the channel', (done) => { + it('should return success with exists=true if given username is a member of the channel', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1849,11 +1845,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', true); - }) - .end(done); + }); }); - it('should return success with exists=false if user is not a member of the channel', (done) => { + it('should return success with exists=false if user is not a member of the channel', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1865,11 +1860,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', false); - }) - .end(done); + }); }); - it('should return success with exists=true if given userId is a member of the group', (done) => { + it('should return success with exists=true if given userId is a member of the group', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1881,11 +1875,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', true); - }) - .end(done); + }); }); - it('should return success with exists=true if given username is a member of the group', (done) => { + it('should return success with exists=true if given username is a member of the group', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1897,11 +1890,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', true); - }) - .end(done); + }); }); - it('should return success with exists=false if user is not a member of the group', (done) => { + it('should return success with exists=false if user is not a member of the group', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1913,11 +1905,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', false); - }) - .end(done); + }); }); - it('should return unauthorized if caller cannot access the group', (done) => { + it('should return unauthorized if caller cannot access the group', () => { request .get(api('rooms.isMember')) .set(testUserNonMemberCredentials) @@ -1929,11 +1920,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); + }); }); - it('should return success with exists=true if given userId is a member of the DM', (done) => { + it('should return success with exists=true if given userId is a member of the DM', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1945,11 +1935,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', true); - }) - .end(done); + }); }); - it('should return success with exists=true if given username is a member of the DM', (done) => { + it('should return success with exists=true if given username is a member of the DM', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1961,11 +1950,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', true); - }) - .end(done); + }); }); - it('should return success with exists=false if user is not a member of the DM', (done) => { + it('should return success with exists=false if user is not a member of the DM', () => { request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -1977,11 +1965,10 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('exists', false); - }) - .end(done); + }); }); - it('should return unauthorized if caller cannot access the DM', (done) => { + it('should return unauthorized if caller cannot access the DM', () => { request .get(api('rooms.isMember')) .set(testUserNonMemberCredentials) @@ -1993,8 +1980,7 @@ describe('[Rooms]', function () { .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); + }); }); }); }); From 5f9e7f5263fd0c042ae06252e6c0e265a662fbd4 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 23 Feb 2024 16:22:46 +0530 Subject: [PATCH 24/39] minor fixes Signed-off-by: Abhinav Kumar --- apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json | 2 +- packages/rest-typings/src/v1/rooms.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 6fce3a71188e..d84f637e5c6b 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -6294,4 +6294,4 @@ "Seat_limit_reached": "Seat limit reached", "Seat_limit_reached_Description": "Your workspace reached its contractual seat limit. Buy more seats to add more users.", "Buy_more_seats": "Buy more seats" -} +} \ No newline at end of file diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 9f8593e801ab..67aa68360ba4 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -428,9 +428,9 @@ type RoomsIsMemberProps = { roomId: string } & ({ username: string } | { userId: const RoomsIsMemberPropsSchema = { type: 'object', properties: { - roomId: { type: 'string' }, - userId: { type: 'string' }, - username: { type: 'string' }, + roomId: { type: 'string', minLength: 1 }, + userId: { type: 'string', minLength: 1 }, + username: { type: 'string', minLength: 1 }, }, oneOf: [{ required: ['roomId', 'userId'] }, { required: ['roomId', 'username'] }], additionalProperties: false, From 68b2bb14776fc8590167761d90d7c2e59a8d4e67 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 23 Feb 2024 18:26:51 +0530 Subject: [PATCH 25/39] minor fixes Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 7079fe063c41..581ab693a697 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1771,7 +1771,7 @@ describe('[Rooms]', function () { ); it('should return error if room not found', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1789,7 +1789,7 @@ describe('[Rooms]', function () { }); it('should return error if user not found with the given userId', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1804,7 +1804,7 @@ describe('[Rooms]', function () { }); it('should return error if user not found with the given username', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1819,7 +1819,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=true if given userId is a member of the channel', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1834,7 +1834,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=true if given username is a member of the channel', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1849,7 +1849,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=false if user is not a member of the channel', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1864,7 +1864,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=true if given userId is a member of the group', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1879,7 +1879,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=true if given username is a member of the group', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1894,7 +1894,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=false if user is not a member of the group', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1909,7 +1909,7 @@ describe('[Rooms]', function () { }); it('should return unauthorized if caller cannot access the group', () => { - request + return request .get(api('rooms.isMember')) .set(testUserNonMemberCredentials) .query({ @@ -1924,7 +1924,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=true if given userId is a member of the DM', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1939,7 +1939,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=true if given username is a member of the DM', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1954,7 +1954,7 @@ describe('[Rooms]', function () { }); it('should return success with exists=false if user is not a member of the DM', () => { - request + return request .get(api('rooms.isMember')) .set(testUser1Credentials) .query({ @@ -1969,7 +1969,7 @@ describe('[Rooms]', function () { }); it('should return unauthorized if caller cannot access the DM', () => { - request + return request .get(api('rooms.isMember')) .set(testUserNonMemberCredentials) .query({ From 50cd2c05671bd96d89c75a6c0f070f16773cb831 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 27 Feb 2024 14:28:17 +0530 Subject: [PATCH 26/39] Update apps/meteor/tests/end-to-end/api/09-rooms.js Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- apps/meteor/tests/end-to-end/api/09-rooms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 581ab693a697..6605ef238bdc 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1707,7 +1707,7 @@ describe('[Rooms]', function () { .end(done); }); }); - describe('/rooms.isMember', async () => { + describe('/rooms.isMember', () => { let testChannel; let testGroup; let testDM; From dfe9eb827d32742991653546f9589bb6037d05b3 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 27 Feb 2024 14:52:49 +0530 Subject: [PATCH 27/39] fixed test 09-rooms.js Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index 6605ef238bdc..b489e23213d3 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -1725,11 +1725,17 @@ describe('[Rooms]', function () { let testUserNonMemberCredentials; before(async () => { - [testUser1, testUser2, testUserNonMember] = await Promise.all([createUser(), createUser(), createUser()]); - [testUser1Credentials, testUserNonMemberCredentials] = await Promise.all([ - login(testUser1.username, password), - login(testUserNonMember.username, password), - ]); + testUser1 = await createUser(); + testUser1Credentials = await login(testUser1.username, password); + }); + + before(async () => { + testUser2 = await createUser(); + }); + + before(async () => { + testUserNonMember = await createUser(); + testUserNonMemberCredentials = await login(testUserNonMember.username, password); }); before(async () => { From 7d142b1cba3ee198cb5810d34fd7801139d5169e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 13 Mar 2024 14:35:19 +0530 Subject: [PATCH 28/39] improved useMemberExists hook Signed-off-by: Abhinav Kumar --- apps/meteor/client/views/hooks/useMemberExists.ts | 6 ++++-- .../room/contextualBar/RoomMembers/RoomMembersActions.tsx | 2 +- .../room/contextualBar/RoomMembers/RoomMembersWithData.tsx | 2 +- .../views/room/contextualBar/UserInfo/UserInfoActions.tsx | 5 ++++- .../views/room/contextualBar/UserInfo/UserInfoWithData.tsx | 7 +++++-- .../room/hooks/useUserInfoActions/useUserInfoActions.ts | 3 ++- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index c66cff15509e..20861080e7ba 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -1,8 +1,10 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -export const useMemberExists = (roomId: string, username: string) => { +export const useMemberExists = (roomId: string, username: string, override?: boolean) => { const checkMember = useEndpoint('GET', '/v1/rooms.isMember'); - return useQuery(['rooms/isMember', roomId, username], () => checkMember({ roomId, username })); + return useQuery(['rooms/isMember', roomId, username, override], async () => { + return override !== undefined ? { exists: override } : checkMember({ roomId, username }); + }); }; diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx index 78d61d9e9f6e..7df7c468bb01 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx @@ -16,7 +16,7 @@ type RoomMembersActionsProps = { const RoomMembersActions = ({ username, _id, name, rid, reload }: RoomMembersActionsProps): ReactElement | null => { const t = useTranslation(); - const { menuActions: menuOptions } = useUserInfoActions({ _id, username, name }, rid, reload, 0); + const { menuActions: menuOptions } = useUserInfoActions({ _id, username, name }, rid, reload, 0, true); if (!menuOptions) { return null; } diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx index 90afbc18ed84..4a470c367de9 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx @@ -81,7 +81,7 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => { }, [setState]); if (state.tab === ROOM_MEMBERS_TABS.INFO && state.userId) { - return ; + return ; } if (state.tab === ROOM_MEMBERS_TABS.INVITE) { diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx index 42004ed228fa..a99f36571bc5 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx @@ -13,14 +13,17 @@ type UserInfoActionsProps = { user: Pick; rid: IRoom['_id']; backToList: () => void; + isMember?: boolean; }; -const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): ReactElement => { +const UserInfoActions = ({ user, rid, backToList, isMember }: UserInfoActionsProps): ReactElement => { const t = useTranslation(); const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions( { _id: user._id, username: user.username, name: user.name }, rid, backToList, + undefined, + isMember, ); const menu = useMemo(() => { diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx index d5e2f36625f4..53650e0d9bad 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx @@ -28,9 +28,10 @@ type UserInfoWithDataProps = { rid: IRoom['_id']; onClose: () => void; onClickBack: () => void; + isMember?: boolean; }; -const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack }: UserInfoWithDataProps): ReactElement => { +const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack, isMember }: UserInfoWithDataProps): ReactElement => { const t = useTranslation(); const getRoles = useRolesDescription(); @@ -104,7 +105,9 @@ const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack }: UserInfo )} - {!isLoading && user && } />} + {!isLoading && user && ( + } /> + )} ); }; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index 59707b59cf25..f337938ced36 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -41,8 +41,9 @@ export const useUserInfoActions = ( rid: IRoom['_id'], reload?: () => void, size = 2, + isMember?: boolean, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { - const { data, refetch, isSuccess: membershipCheckSuccess } = useMemberExists(rid, user.username as string); + const { data, refetch, isSuccess: membershipCheckSuccess } = useMemberExists(rid, user.username as string, isMember); const memberChangeReload = useCallback(async () => { await reload?.(); await refetch(); From 76a71ee1762fa4d8939c870c1ddf2234b8b8035f Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 14 Mar 2024 21:40:52 +0530 Subject: [PATCH 29/39] updated useMemberExists Signed-off-by: Abhinav Kumar --- apps/meteor/client/views/hooks/useMemberExists.ts | 8 +++++--- .../room/hooks/useUserInfoActions/useUserInfoActions.ts | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index 20861080e7ba..12af3f758c61 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -1,10 +1,12 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -export const useMemberExists = (roomId: string, username: string, override?: boolean) => { +type UseMemberExistsProps = { roomId: string; username: string; isMember?: boolean }; + +export const useMemberExists = ({ roomId, username, isMember }: UseMemberExistsProps) => { const checkMember = useEndpoint('GET', '/v1/rooms.isMember'); - return useQuery(['rooms/isMember', roomId, username, override], async () => { - return override !== undefined ? { exists: override } : checkMember({ roomId, username }); + return useQuery(['rooms/isMember', roomId, username, isMember], async () => { + return isMember !== undefined ? { exists: isMember } : checkMember({ roomId, username }); }); }; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index f337938ced36..817bc5d3f8d4 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -43,7 +43,11 @@ export const useUserInfoActions = ( size = 2, isMember?: boolean, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { - const { data, refetch, isSuccess: membershipCheckSuccess } = useMemberExists(rid, user.username as string, isMember); + const { + data, + refetch, + isSuccess: membershipCheckSuccess, + } = useMemberExists({ roomId: rid, username: user.username as string, isMember }); const memberChangeReload = useCallback(async () => { await reload?.(); await refetch(); From d3f53ffe00d815c1dfe707096d155961eccf08c2 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 15 Mar 2024 20:57:47 +0530 Subject: [PATCH 30/39] fix review --- .../client/views/hooks/useMemberExists.ts | 8 ++--- .../views/room/UserCard/UserCardWithData.tsx | 7 ++++ .../useUserInfoActions/useUserInfoActions.ts | 36 +++++++------------ 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/apps/meteor/client/views/hooks/useMemberExists.ts b/apps/meteor/client/views/hooks/useMemberExists.ts index 12af3f758c61..f737ea6c4e94 100644 --- a/apps/meteor/client/views/hooks/useMemberExists.ts +++ b/apps/meteor/client/views/hooks/useMemberExists.ts @@ -1,12 +1,10 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -type UseMemberExistsProps = { roomId: string; username: string; isMember?: boolean }; +type UseMemberExistsProps = { roomId: string; username: string }; -export const useMemberExists = ({ roomId, username, isMember }: UseMemberExistsProps) => { +export const useMemberExists = ({ roomId, username }: UseMemberExistsProps) => { const checkMember = useEndpoint('GET', '/v1/rooms.isMember'); - return useQuery(['rooms/isMember', roomId, username, isMember], async () => { - return isMember !== undefined ? { exists: isMember } : checkMember({ roomId, username }); - }); + return useQuery(['rooms/isMember', roomId, username], () => checkMember({ roomId, username })); }; diff --git a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx index 40244b04beba..c6f58d233e25 100644 --- a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx +++ b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx @@ -10,6 +10,7 @@ import LocalTime from '../../../components/LocalTime'; import { UserCard, UserCardAction, UserCardRole, UserCardSkeleton } from '../../../components/UserCard'; import { ReactiveUserStatus } from '../../../components/UserStatus'; import { useUserInfoQuery } from '../../../hooks/useUserInfoQuery'; +import { useMemberExists } from '../../hooks/useMemberExists'; import { useUserInfoActions } from '../hooks/useUserInfoActions'; type UserCardWithDataProps = { @@ -25,6 +26,9 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi const showRealNames = Boolean(useSetting('UI_Use_Real_Name')); const { data, isLoading } = useUserInfoQuery({ username }); + const { data: isMemberData, refetch, isSuccess: membershipCheckSuccess } = useMemberExists({ roomId: rid, username }); + + const isMember = (isMemberData?.exists as boolean) && membershipCheckSuccess; const user = useMemo(() => { const defaultValue = isLoading ? undefined : null; @@ -62,6 +66,9 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions( { _id: user._id ?? '', username: user.username, name: user.name }, rid, + refetch, + undefined, + isMember, ); const menu = useMemo(() => { diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts index 817bc5d3f8d4..2a70ff55ee34 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -2,11 +2,10 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; import type { Icon } from '@rocket.chat/fuselage'; import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; import type { ComponentProps } from 'react'; -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import { useEmbeddedLayout } from '../../../../hooks/useEmbeddedLayout'; -import { useMemberExists } from '../../../hooks/useMemberExists'; import { useAddUserAction } from './actions/useAddUserAction'; import { useBlockUserAction } from './actions/useBlockUserAction'; import { useCallAction } from './actions/useCallAction'; @@ -43,18 +42,7 @@ export const useUserInfoActions = ( size = 2, isMember?: boolean, ): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => { - const { - data, - refetch, - isSuccess: membershipCheckSuccess, - } = useMemberExists({ roomId: rid, username: user.username as string, isMember }); - const memberChangeReload = useCallback(async () => { - await reload?.(); - await refetch(); - }, [reload, refetch]); - const showAddMember = (data?.exists as boolean) && membershipCheckSuccess; - - const addUser = useAddUserAction(user, rid, memberChangeReload); + const addUser = useAddUserAction(user, rid, reload); const blockUser = useBlockUserAction(user, rid); const changeLeader = useChangeLeaderAction(user, rid); const changeModerator = useChangeModeratorAction(user, rid); @@ -63,7 +51,7 @@ export const useUserInfoActions = ( const openDirectMessage = useDirectMessageAction(user, rid); const ignoreUser = useIgnoreUserAction(user, rid); const muteUser = useMuteUserAction(user, rid); - const removeUser = useRemoveUserAction(user, rid, memberChangeReload); + const removeUser = useRemoveUserAction(user, rid, reload); const call = useCallAction(user); const reportUserOption = useReportUser(user); const isLayoutEmbedded = useEmbeddedLayout(); @@ -73,16 +61,16 @@ export const useUserInfoActions = ( () => ({ ...(openDirectMessage && !isLayoutEmbedded && { openDirectMessage }), ...(call && { call }), - ...(!showAddMember && addUser && { addUser }), - ...(showAddMember && changeOwner && { changeOwner }), - ...(showAddMember && changeLeader && { changeLeader }), - ...(showAddMember && changeModerator && { changeModerator }), - ...(showAddMember && openModerationConsole && { openModerationConsole }), - ...(showAddMember && ignoreUser && { ignoreUser }), - ...(showAddMember && muteUser && { muteUser }), + ...(!isMember && addUser && { addUser }), + ...(isMember && changeOwner && { changeOwner }), + ...(isMember && changeLeader && { changeLeader }), + ...(isMember && changeModerator && { changeModerator }), + ...(isMember && openModerationConsole && { openModerationConsole }), + ...(isMember && ignoreUser && { ignoreUser }), + ...(isMember && muteUser && { muteUser }), ...(blockUser && { toggleBlock: blockUser }), ...(reportUserOption && { reportUser: reportUserOption }), - ...(showAddMember && removeUser && { removeUser }), + ...(isMember && removeUser && { removeUser }), }), [ openDirectMessage, @@ -98,7 +86,7 @@ export const useUserInfoActions = ( reportUserOption, openModerationConsole, addUser, - showAddMember, + isMember, ], ); From 910b67c6cdbd5973b710be5913d8f303ea4ce824 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 15 Mar 2024 21:47:29 +0530 Subject: [PATCH 31/39] minor fix Signed-off-by: Abhinav Kumar --- .../RoomMembers/RoomMembersWithData.tsx | 2 +- .../contextualBar/UserInfo/UserInfoActions.tsx | 17 ++++++++++++++--- .../contextualBar/UserInfo/UserInfoWithData.tsx | 7 ++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx index 4a470c367de9..90afbc18ed84 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx @@ -81,7 +81,7 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => { }, [setState]); if (state.tab === ROOM_MEMBERS_TABS.INFO && state.userId) { - return ; + return ; } if (state.tab === ROOM_MEMBERS_TABS.INVITE) { diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx index a99f36571bc5..07aa3628b0c1 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx @@ -7,21 +7,32 @@ import React, { useMemo } from 'react'; import GenericMenu from '../../../../components/GenericMenu/GenericMenu'; import UserInfo from '../../../../components/UserInfo'; +import { useMemberExists } from '../../../hooks/useMemberExists'; import { useUserInfoActions } from '../../hooks/useUserInfoActions'; type UserInfoActionsProps = { user: Pick; rid: IRoom['_id']; backToList: () => void; - isMember?: boolean; }; -const UserInfoActions = ({ user, rid, backToList, isMember }: UserInfoActionsProps): ReactElement => { +const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): ReactElement => { const t = useTranslation(); + const { + data: isMemberData, + refetch, + isSuccess: membershipCheckSuccess, + } = useMemberExists({ roomId: rid, username: user.username as string }); + + const isMember = (isMemberData?.exists as boolean) && membershipCheckSuccess; + const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions( { _id: user._id, username: user.username, name: user.name }, rid, - backToList, + () => { + backToList?.(); + refetch(); + }, undefined, isMember, ); diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx index 53650e0d9bad..d5e2f36625f4 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx @@ -28,10 +28,9 @@ type UserInfoWithDataProps = { rid: IRoom['_id']; onClose: () => void; onClickBack: () => void; - isMember?: boolean; }; -const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack, isMember }: UserInfoWithDataProps): ReactElement => { +const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack }: UserInfoWithDataProps): ReactElement => { const t = useTranslation(); const getRoles = useRolesDescription(); @@ -105,9 +104,7 @@ const UserInfoWithData = ({ uid, username, rid, onClose, onClickBack, isMember } )} - {!isLoading && user && ( - } /> - )} + {!isLoading && user && } />} ); }; From 172ad7ab2978d8b261e58a7b6853ba4fbc6f7a7e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Wed, 27 Mar 2024 16:29:10 +0530 Subject: [PATCH 32/39] fixed loading to reflect ui Signed-off-by: Abhinav Kumar --- .../client/views/room/UserCard/UserCardWithData.tsx | 10 ++++++++-- .../room/contextualBar/UserInfo/UserInfoActions.tsx | 6 +++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx index c6f58d233e25..1c6ceaea5f4b 100644 --- a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx +++ b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx @@ -25,9 +25,15 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi const getRoles = useRolesDescription(); const showRealNames = Boolean(useSetting('UI_Use_Real_Name')); - const { data, isLoading } = useUserInfoQuery({ username }); - const { data: isMemberData, refetch, isSuccess: membershipCheckSuccess } = useMemberExists({ roomId: rid, username }); + const { data, isLoading: isUserInfoLoading } = useUserInfoQuery({ username }); + const { + data: isMemberData, + refetch, + isSuccess: membershipCheckSuccess, + isLoading: isMembershipStatusLoading, + } = useMemberExists({ roomId: rid, username }); + const isLoading = isUserInfoLoading || isMembershipStatusLoading; const isMember = (isMemberData?.exists as boolean) && membershipCheckSuccess; const user = useMemo(() => { diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx index 07aa3628b0c1..eb07b951ddff 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/display-name, react/no-multi-comp */ import type { IRoom, IUser } from '@rocket.chat/core-typings'; -import { ButtonGroup, IconButton } from '@rocket.chat/fuselage'; +import { ButtonGroup, IconButton, Skeleton } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useMemo } from 'react'; @@ -22,6 +22,7 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React data: isMemberData, refetch, isSuccess: membershipCheckSuccess, + isLoading, } = useMemberExists({ roomId: rid, username: user.username as string }); const isMember = (isMemberData?.exists as boolean) && membershipCheckSuccess; @@ -65,6 +66,9 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React return [...actionsDefinition.map(mapAction), menu].filter(Boolean); }, [actionsDefinition, menu]); + if (isLoading) { + return ; + } return {actions}; }; From 8e7de2783cdcfb63cc6298182304c4ca1aa50634 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Thu, 13 Jun 2024 22:58:20 +0530 Subject: [PATCH 33/39] added ui e2e tests Signed-off-by: Abhinav Kumar --- .../tests/e2e/user-card-info-in-room.spec.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 apps/meteor/tests/e2e/user-card-info-in-room.spec.ts diff --git a/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts b/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts new file mode 100644 index 000000000000..38f5aa579940 --- /dev/null +++ b/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts @@ -0,0 +1,57 @@ +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { createTargetChannel, deleteChannel } from './utils'; +import { expect, test } from './utils/test'; + +test.use({ storageState: Users.admin.state }); + +test.describe.parallel('Mention User Card', () => { + let poHomeChannel: HomeChannel; + let targetChannel: string; + + test.beforeAll(async ({ api }) => { + targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username] }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto('/home'); + }); + + test.afterAll(({ api }) => deleteChannel(api, targetChannel)); + + test('should show correct userinfo actions for a member of the room', async ({ page }) => { + const { username } = Users.user1.data; + await poHomeChannel.sidenav.openChat(targetChannel); + await poHomeChannel.content.sendMessage(`Hello @${username}`); + await page.locator('button[aria-label="Send"]').click(); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${username}"]`); + await mentionSpan.click(); + + await expect(page.locator('button[title="Add to room"]')).not.toBeVisible(); + + await page.locator('div[aria-label="User card actions"] button[title="More"]').click(); + + await expect(page.locator('label[data-key="Remove from room"]')).toBeVisible(); + await expect(page.locator('label[data-key="Set as leader"]')).toBeVisible(); + await expect(page.locator('label[data-key="Set as moderator"]')).toBeVisible(); + }); + + test('should show correct userinfo actions for a non-member of the room', async ({ page }) => { + const { username } = Users.user2.data; + await poHomeChannel.sidenav.openChat(targetChannel); + await poHomeChannel.content.sendMessage(`Hello @${username}`); + await page.locator('button[aria-label="Send"]').click(); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${username}"]`); + await mentionSpan.click(); + + await expect(page.locator('button[title="Add to room"]')).toBeVisible(); + + await page.locator('div[aria-label="User card actions"] button[title="More"]').click(); + + await expect(page.locator('label[data-key="Remove from room"]')).not.toBeVisible(); + await expect(page.locator('label[data-key="Set as leader"]')).not.toBeVisible(); + await expect(page.locator('label[data-key="Set as moderator"]')).not.toBeVisible(); + }); +}); From 852d30435b13a24a7aec39c18d4b3942e8abfda9 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 14 Jun 2024 00:34:20 +0530 Subject: [PATCH 34/39] improved tests Signed-off-by: Abhinav Kumar --- .../tests/e2e/user-card-info-in-room.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts b/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts index 38f5aa579940..2c41806c335b 100644 --- a/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts +++ b/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts @@ -11,6 +11,11 @@ test.describe.parallel('Mention User Card', () => { test.beforeAll(async ({ api }) => { targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username] }); + + await api.post(`/chat.postMessage`, { + text: `Hello @${Users.user1.data.username} @${Users.user2.data.username}`, + channel: targetChannel, + }); }); test.beforeEach(async ({ page }) => { @@ -22,11 +27,8 @@ test.describe.parallel('Mention User Card', () => { test.afterAll(({ api }) => deleteChannel(api, targetChannel)); test('should show correct userinfo actions for a member of the room', async ({ page }) => { - const { username } = Users.user1.data; await poHomeChannel.sidenav.openChat(targetChannel); - await poHomeChannel.content.sendMessage(`Hello @${username}`); - await page.locator('button[aria-label="Send"]').click(); - const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${username}"]`); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user1.data.username}"]`); await mentionSpan.click(); await expect(page.locator('button[title="Add to room"]')).not.toBeVisible(); @@ -39,11 +41,8 @@ test.describe.parallel('Mention User Card', () => { }); test('should show correct userinfo actions for a non-member of the room', async ({ page }) => { - const { username } = Users.user2.data; await poHomeChannel.sidenav.openChat(targetChannel); - await poHomeChannel.content.sendMessage(`Hello @${username}`); - await page.locator('button[aria-label="Send"]').click(); - const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${username}"]`); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user2.data.username}"]`); await mentionSpan.click(); await expect(page.locator('button[title="Add to room"]')).toBeVisible(); From 5c383ec67ef0e7840f3f87c609ed12e34c44f848 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 18 Jun 2024 20:36:39 +0530 Subject: [PATCH 35/39] minor refactor Signed-off-by: Abhinav Kumar --- apps/meteor/client/views/room/UserCard/UserCardWithData.tsx | 2 +- .../views/room/contextualBar/UserInfo/UserInfoActions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx index 1c6ceaea5f4b..cada698e8e48 100644 --- a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx +++ b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx @@ -34,7 +34,7 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi } = useMemberExists({ roomId: rid, username }); const isLoading = isUserInfoLoading || isMembershipStatusLoading; - const isMember = (isMemberData?.exists as boolean) && membershipCheckSuccess; + const isMember = membershipCheckSuccess && isMemberData?.exists; const user = useMemo(() => { const defaultValue = isLoading ? undefined : null; diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx index eb07b951ddff..254bde825bc9 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx @@ -25,7 +25,7 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React isLoading, } = useMemberExists({ roomId: rid, username: user.username as string }); - const isMember = (isMemberData?.exists as boolean) && membershipCheckSuccess; + const isMember = membershipCheckSuccess && isMemberData?.exists; const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions( { _id: user._id, username: user.username, name: user.name }, From b68f6abc3783d7834c01db39ca2b7424c40ff626 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Sat, 22 Jun 2024 02:50:24 +0530 Subject: [PATCH 36/39] minor fixes and improved tests Signed-off-by: Abhinav Kumar --- apps/meteor/app/api/server/v1/rooms.ts | 2 +- .../user-card-info-actions-by-member.spec.ts | 90 +++++++++++++++++++ ...er-card-info-actions-by-room-owner.spec.ts | 90 +++++++++++++++++++ .../tests/e2e/user-card-info-in-room.spec.ts | 56 ------------ 4 files changed, 181 insertions(+), 57 deletions(-) create mode 100644 apps/meteor/tests/e2e/user-card-info-actions-by-member.spec.ts create mode 100644 apps/meteor/tests/e2e/user-card-info-actions-by-room-owner.spec.ts delete mode 100644 apps/meteor/tests/e2e/user-card-info-in-room.spec.ts diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 307ba3dc7a92..06ff971a1a0a 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -696,7 +696,7 @@ API.v1.addRoute( return API.v1.failure('error-user-not-found'); } - if (await canAccessRoomAsync(room, this.user)) { + if (await canAccessRoomAsync(room, { _id: this.user._id })) { return API.v1.success({ exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0, }); diff --git a/apps/meteor/tests/e2e/user-card-info-actions-by-member.spec.ts b/apps/meteor/tests/e2e/user-card-info-actions-by-member.spec.ts new file mode 100644 index 000000000000..b664f2c9ee69 --- /dev/null +++ b/apps/meteor/tests/e2e/user-card-info-actions-by-member.spec.ts @@ -0,0 +1,90 @@ +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { createTargetChannel, deleteChannel } from './utils'; +import { expect, test } from './utils/test'; + +test.use({ storageState: Users.user3.state }); + +test.describe.parallel('Mention User Card [To Member]', () => { + let poHomeChannel: HomeChannel; + let targetChannel: string; + + test.beforeAll(async ({ api }) => { + targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username, Users.user3.data.username] }); + + await api.post(`/chat.postMessage`, { + text: `Hello @${Users.user1.data.username} @${Users.user2.data.username}`, + channel: targetChannel, + }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto('/home'); + }); + + test.afterAll(({ api }) => deleteChannel(api, targetChannel)); + + test('should show correct userinfo actions for a member of the room to a non-privileged member', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user1.data.username}"]`); + await mentionSpan.click(); + + await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible(); + const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]'); + if (await moreButton.isVisible()) { + await moreButton.click(); + } + + const isAddToRoomVisible = + (await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible()); + await expect(isAddToRoomVisible).toBeFalsy(); + + const isRemoveFromRoomVisible = + (await page.locator('button[title="Remove from room"]').isVisible()) || + (await page.locator('label[data-key="Remove from room"]').isVisible()); + await expect(isRemoveFromRoomVisible).toBeFalsy(); + + const isSetAsLeaderVisible = + (await page.locator('button[title="Set as leader"]').isVisible()) || + (await page.locator('label[data-key="Set as leader"]').isVisible()); + await expect(isSetAsLeaderVisible).toBeFalsy(); + + const isSetAsModeratorVisible = + (await page.locator('button[title="Set as moderator"]').isVisible()) || + (await page.locator('label[data-key="Set as moderator"]').isVisible()); + await expect(isSetAsModeratorVisible).toBeFalsy(); + }); + + test('should show correct userinfo actions for a non-member of the room to a non-privileged member', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user2.data.username}"]`); + await mentionSpan.click(); + + await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible(); + const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]'); + if (await moreButton.isVisible()) { + await moreButton.click(); + } + + const isAddToRoomVisible = + (await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible()); + await expect(isAddToRoomVisible).toBeFalsy(); + + const isRemoveFromRoomVisible = + (await page.locator('button[title="Remove from room"]').isVisible()) || + (await page.locator('label[data-key="Remove from room"]').isVisible()); + await expect(isRemoveFromRoomVisible).toBeFalsy(); + + const isSetAsLeaderVisible = + (await page.locator('button[title="Set as leader"]').isVisible()) || + (await page.locator('label[data-key="Set as leader"]').isVisible()); + await expect(isSetAsLeaderVisible).toBeFalsy(); + + const isSetAsModeratorVisible = + (await page.locator('button[title="Set as moderator"]').isVisible()) || + (await page.locator('label[data-key="Set as moderator"]').isVisible()); + await expect(isSetAsModeratorVisible).toBeFalsy(); + }); +}); diff --git a/apps/meteor/tests/e2e/user-card-info-actions-by-room-owner.spec.ts b/apps/meteor/tests/e2e/user-card-info-actions-by-room-owner.spec.ts new file mode 100644 index 000000000000..808acbb79bbd --- /dev/null +++ b/apps/meteor/tests/e2e/user-card-info-actions-by-room-owner.spec.ts @@ -0,0 +1,90 @@ +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { createTargetChannel, deleteChannel } from './utils'; +import { expect, test } from './utils/test'; + +test.use({ storageState: Users.admin.state }); +test.describe.parallel('Mention User Card [To Room Owner]', () => { + let poHomeChannel: HomeChannel; + let targetChannel: string; + + test.beforeAll(async ({ api }) => { + targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username] }); + + await api.post(`/chat.postMessage`, { + text: `Hello @${Users.user1.data.username} @${Users.user2.data.username}`, + channel: targetChannel, + }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto('/home'); + }); + + test.afterAll(({ api }) => deleteChannel(api, targetChannel)); + + test('should show correct userinfo actions for a member of the room to the room owner', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user1.data.username}"]`); + await mentionSpan.click(); + + await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible(); + const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]'); + + if (await moreButton.isVisible()) { + await moreButton.click(); + } + + const isAddToRoomVisible = + (await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible()); + await expect(isAddToRoomVisible).toBeFalsy(); + + const isRemoveFromRoomVisible = + (await page.locator('button[title="Remove from room"]').isVisible()) || + (await page.locator('label[data-key="Remove from room"]').isVisible()); + await expect(isRemoveFromRoomVisible).toBeTruthy(); + + const isSetAsLeaderVisible = + (await page.locator('button[title="Set as leader"]').isVisible()) || + (await page.locator('label[data-key="Set as leader"]').isVisible()); + await expect(isSetAsLeaderVisible).toBeTruthy(); + + const isSetAsModeratorVisible = + (await page.locator('button[title="Set as moderator"]').isVisible()) || + (await page.locator('label[data-key="Set as moderator"]').isVisible()); + await expect(isSetAsModeratorVisible).toBeTruthy(); + }); + + test('should show correct userinfo actions for a non-member of the room to the room owner', async ({ page }) => { + await poHomeChannel.sidenav.openChat(targetChannel); + const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user2.data.username}"]`); + await mentionSpan.click(); + + await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible(); + const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]'); + if (await moreButton.isVisible()) { + await moreButton.click(); + } + + const isAddToRoomVisible = + (await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible()); + await expect(isAddToRoomVisible).toBeTruthy(); + + const isRemoveFromRoomVisible = + (await page.locator('button[title="Remove from room"]').isVisible()) || + (await page.locator('label[data-key="Remove from room"]').isVisible()); + await expect(isRemoveFromRoomVisible).toBeFalsy(); + + const isSetAsLeaderVisible = + (await page.locator('button[title="Set as leader"]').isVisible()) || + (await page.locator('label[data-key="Set as leader"]').isVisible()); + await expect(isSetAsLeaderVisible).toBeFalsy(); + + const isSetAsModeratorVisible = + (await page.locator('button[title="Set as moderator"]').isVisible()) || + (await page.locator('label[data-key="Set as moderator"]').isVisible()); + await expect(isSetAsModeratorVisible).toBeFalsy(); + }); +}); diff --git a/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts b/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts deleted file mode 100644 index 2c41806c335b..000000000000 --- a/apps/meteor/tests/e2e/user-card-info-in-room.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Users } from './fixtures/userStates'; -import { HomeChannel } from './page-objects'; -import { createTargetChannel, deleteChannel } from './utils'; -import { expect, test } from './utils/test'; - -test.use({ storageState: Users.admin.state }); - -test.describe.parallel('Mention User Card', () => { - let poHomeChannel: HomeChannel; - let targetChannel: string; - - test.beforeAll(async ({ api }) => { - targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username] }); - - await api.post(`/chat.postMessage`, { - text: `Hello @${Users.user1.data.username} @${Users.user2.data.username}`, - channel: targetChannel, - }); - }); - - test.beforeEach(async ({ page }) => { - poHomeChannel = new HomeChannel(page); - - await page.goto('/home'); - }); - - test.afterAll(({ api }) => deleteChannel(api, targetChannel)); - - test('should show correct userinfo actions for a member of the room', async ({ page }) => { - await poHomeChannel.sidenav.openChat(targetChannel); - const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user1.data.username}"]`); - await mentionSpan.click(); - - await expect(page.locator('button[title="Add to room"]')).not.toBeVisible(); - - await page.locator('div[aria-label="User card actions"] button[title="More"]').click(); - - await expect(page.locator('label[data-key="Remove from room"]')).toBeVisible(); - await expect(page.locator('label[data-key="Set as leader"]')).toBeVisible(); - await expect(page.locator('label[data-key="Set as moderator"]')).toBeVisible(); - }); - - test('should show correct userinfo actions for a non-member of the room', async ({ page }) => { - await poHomeChannel.sidenav.openChat(targetChannel); - const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user2.data.username}"]`); - await mentionSpan.click(); - - await expect(page.locator('button[title="Add to room"]')).toBeVisible(); - - await page.locator('div[aria-label="User card actions"] button[title="More"]').click(); - - await expect(page.locator('label[data-key="Remove from room"]')).not.toBeVisible(); - await expect(page.locator('label[data-key="Set as leader"]')).not.toBeVisible(); - await expect(page.locator('label[data-key="Set as moderator"]')).not.toBeVisible(); - }); -}); From 1cee4b25fd8ea86011aa943c7d1c0a6438b45c2b Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 25 Jun 2024 21:28:54 +0530 Subject: [PATCH 37/39] modified rooms.isMember response Signed-off-by: Abhinav Kumar --- apps/meteor/app/api/server/v1/rooms.ts | 2 +- .../views/room/UserCard/UserCardWithData.tsx | 2 +- .../UserInfo/UserInfoActions.tsx | 2 +- apps/meteor/tests/end-to-end/api/09-rooms.js | 36 +++++++++---------- packages/rest-typings/src/v1/rooms.ts | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 06ff971a1a0a..91f1ee24a495 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -698,7 +698,7 @@ API.v1.addRoute( if (await canAccessRoomAsync(room, { _id: this.user._id })) { return API.v1.success({ - exists: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0, + isMember: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0, }); } return API.v1.unauthorized(); diff --git a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx index a11ff3821a85..a12e9a143cc2 100644 --- a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx +++ b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx @@ -34,7 +34,7 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi } = useMemberExists({ roomId: rid, username }); const isLoading = isUserInfoLoading || isMembershipStatusLoading; - const isMember = membershipCheckSuccess && isMemberData?.exists; + const isMember = membershipCheckSuccess && isMemberData?.isMember; const user = useMemo(() => { const defaultValue = isLoading ? undefined : null; diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx index 254bde825bc9..492e50c0efb1 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx @@ -25,7 +25,7 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React isLoading, } = useMemberExists({ roomId: rid, username: user.username as string }); - const isMember = membershipCheckSuccess && isMemberData?.exists; + const isMember = membershipCheckSuccess && isMemberData?.isMember; const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions( { _id: user._id, username: user.username, name: user.name }, diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js index d5993e323a2d..b4a429276b6f 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -2624,7 +2624,7 @@ describe('[Rooms]', function () { }); }); - it('should return success with exists=true if given userId is a member of the channel', () => { + it('should return success with isMember=true if given userId is a member of the channel', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2635,11 +2635,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', true); + expect(res.body).to.have.property('isMember', true); }); }); - it('should return success with exists=true if given username is a member of the channel', () => { + it('should return success with isMember=true if given username is a member of the channel', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2650,11 +2650,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', true); + expect(res.body).to.have.property('isMember', true); }); }); - it('should return success with exists=false if user is not a member of the channel', () => { + it('should return success with isMember=false if user is not a member of the channel', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2665,11 +2665,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', false); + expect(res.body).to.have.property('isMember', false); }); }); - it('should return success with exists=true if given userId is a member of the group', () => { + it('should return success with isMember=true if given userId is a member of the group', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2680,11 +2680,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', true); + expect(res.body).to.have.property('isMember', true); }); }); - it('should return success with exists=true if given username is a member of the group', () => { + it('should return success with isMember=true if given username is a member of the group', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2695,11 +2695,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', true); + expect(res.body).to.have.property('isMember', true); }); }); - it('should return success with exists=false if user is not a member of the group', () => { + it('should return success with isMember=false if user is not a member of the group', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2710,7 +2710,7 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', false); + expect(res.body).to.have.property('isMember', false); }); }); @@ -2729,7 +2729,7 @@ describe('[Rooms]', function () { }); }); - it('should return success with exists=true if given userId is a member of the DM', () => { + it('should return success with isMember=true if given userId is a member of the DM', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2740,11 +2740,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', true); + expect(res.body).to.have.property('isMember', true); }); }); - it('should return success with exists=true if given username is a member of the DM', () => { + it('should return success with isMember=true if given username is a member of the DM', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2755,11 +2755,11 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', true); + expect(res.body).to.have.property('isMember', true); }); }); - it('should return success with exists=false if user is not a member of the DM', () => { + it('should return success with isMember=false if user is not a member of the DM', () => { return request .get(api('rooms.isMember')) .set(testUser1Credentials) @@ -2770,7 +2770,7 @@ describe('[Rooms]', function () { .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('exists', false); + expect(res.body).to.have.property('isMember', false); }); }); diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 5bca3b251616..a24c1e06970b 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -682,7 +682,7 @@ export type RoomsEndpoints = { }; '/v1/rooms.isMember': { - GET: (params: RoomsIsMemberProps) => { exists: boolean }; + GET: (params: RoomsIsMemberProps) => { isMember: boolean }; }; '/v1/rooms.muteUser': { From 01b1a692c63d46bb1bb56a23156f18ab6da04052 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Tue, 25 Jun 2024 22:39:33 +0530 Subject: [PATCH 38/39] fixed types Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.ts b/apps/meteor/tests/end-to-end/api/09-rooms.ts index 6d9ec63fb468..5e4bb6a3d4bd 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/09-rooms.ts @@ -2860,9 +2860,9 @@ describe('[Rooms]', () => { }); }); describe('/rooms.isMember', () => { - let testChannel; - let testGroup; - let testDM; + let testChannel: IRoom; + let testGroup: IRoom; + let testDM: IRoom; const fakeRoomId = `room.test.${Date.now()}-${Math.random()}`; const fakeUserId = `user.test.${Date.now()}-${Math.random()}`; @@ -2870,11 +2870,11 @@ describe('[Rooms]', () => { const testChannelName = `channel.test.${Date.now()}-${Math.random()}`; const testGroupName = `group.test.${Date.now()}-${Math.random()}`; - let testUser1; - let testUser2; - let testUserNonMember; - let testUser1Credentials; - let testUserNonMemberCredentials; + let testUser1: TestUser; + let testUser2: TestUser; + let testUserNonMember: TestUser; + let testUser1Credentials: Credentials; + let testUserNonMemberCredentials: Credentials; before(async () => { testUser1 = await createUser(); From fdad230c0dd1de397f79ca8d397262379cac1e3a Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 28 Jun 2024 18:14:58 +0530 Subject: [PATCH 39/39] fix typo Signed-off-by: Abhinav Kumar --- apps/meteor/tests/end-to-end/api/09-rooms.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.ts b/apps/meteor/tests/end-to-end/api/09-rooms.ts index 5e4bb6a3d4bd..17d976793403 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/09-rooms.ts @@ -2997,7 +2997,7 @@ describe('[Rooms]', () => { .set(testUser1Credentials) .query({ roomId: testChannel._id, - userId: testUser2.username, + username: testUser2.username, }) .expect(200) .expect((res) => { @@ -3042,7 +3042,7 @@ describe('[Rooms]', () => { .set(testUser1Credentials) .query({ roomId: testGroup._id, - userId: testUser2.username, + username: testUser2.username, }) .expect(200) .expect((res) => { @@ -3102,7 +3102,7 @@ describe('[Rooms]', () => { .set(testUser1Credentials) .query({ roomId: testDM._id, - userId: testUser2.username, + username: testUser2.username, }) .expect(200) .expect((res) => {